Skip to content
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

Add WaitStatus::PtraceSyscall for use with PTRACE_O_TRACESYSGOOD #566

Merged
merged 2 commits into from
Jul 21, 2017
Merged
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: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Added `cfmakeraw`, `cfsetspeed`, and `tcgetsid`. ([#527](https://github.com/nix-rust/nix/pull/527))
- Added "bad none", "bad write_ptr", "bad write_int", and "bad readwrite" variants to the `ioctl!`
macro. ([#670](https://github.com/nix-rust/nix/pull/670))
- On Linux and Android, added support for receiving `PTRACE_O_TRACESYSGOOD`
events from `wait` and `waitpid` using `WaitStatus::PtraceSyscall`
([#566](https://github.com/nix-rust/nix/pull/566)).

### Changed
- Changed `ioctl!(write ...)` into `ioctl!(write_ptr ...)` and `ioctl!(write_int ..)` variants
Expand Down
59 changes: 57 additions & 2 deletions src/sys/wait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,56 @@ libc_bitflags!(
target_os = "android"))]
const WSTOPPED: WaitPidFlag = WUNTRACED;

/// Possible return values from `wait()` or `waitpid()`.
///
/// Each status (other than `StillAlive`) describes a state transition
/// in a child process `Pid`, such as the process exiting or stopping,
/// plus additional data about the transition if any.
///
/// Note that there are two Linux-specific enum variants, `PtraceEvent`
/// and `PtraceSyscall`. Portable code should avoid exhaustively
/// matching on `WaitStatus`.
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub enum WaitStatus {
/// The process exited normally (as with `exit()` or returning from
/// `main`) with the given exit code. This case matches the C macro
/// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`.
Exited(Pid, i8),
/// The process was killed by the given signal. The third field
/// indicates whether the signal generated a core dump. This case
/// matches the C macro `WIFSIGNALED(status)`; the last two fields
/// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`.
Signaled(Pid, Signal, bool),
/// The process is alive, but was stopped by the given signal. This
/// is only reported if `WaitPidFlag::WUNTRACED` was passed. This
/// case matches the C macro `WIFSTOPPED(status)`; the second field
/// is `WSTOPSIG(status)`.
Stopped(Pid, Signal),
/// The traced process was stopped by a `PTRACE_EVENT_*` event. See
/// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All
/// currently-defined events use `SIGTRAP` as the signal; the third
/// field is the `PTRACE_EVENT_*` value of the event.
///
/// [`nix::sys::ptrace`]: ../ptrace/index.html
/// [`ptrace`(2)]: http://man7.org/linux/man-pages/man2/ptrace.2.html
#[cfg(any(target_os = "linux", target_os = "android"))]
PtraceEvent(Pid, Signal, c_int),
/// The traced process was stopped by execution of a system call,
/// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for
/// more information.
///
/// [`ptrace`(2)]: http://man7.org/linux/man-pages/man2/ptrace.2.html
#[cfg(any(target_os = "linux", target_os = "android"))]
PtraceSyscall(Pid),
/// The process was previously stopped but has resumed execution
/// after receiving a `SIGCONT` signal. This is only reported if
/// `WaitPidFlag::WCONTINUED` was passed. This case matches the C
/// macro `WIFCONTINUED(status)`.
Continued(Pid),
/// There are currently no state changes to report in any awaited
/// child process. This is only returned if `WaitPidFlag::WNOHANG`
/// was used (otherwise `wait()` or `waitpid()` would block until
/// there was something to report).
StillAlive
}

Expand All @@ -56,6 +98,7 @@ pub enum WaitStatus {
mod status {
use sys::signal::Signal;
use libc::c_int;
use libc::SIGTRAP;

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

pub fn stop_signal(status: i32) -> Signal {
Copy link
Contributor

Choose a reason for hiding this comment

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

This function needs doc comments as well.

Signal::from_c_int((status & 0xFF00) >> 8).unwrap()
// Keep only 7 bits of the signal: the high bit
// is used to indicate syscall stops, below.
Signal::from_c_int((status & 0x7F00) >> 8).unwrap()
}

pub fn syscall_stop(status: i32) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Needs doccomments for the function.

// From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
// of delivering SIGTRAP | 0x80 as the signal number for syscall
// stops. This allows easily distinguishing syscall stops from
// genuine SIGTRAP signals.
((status & 0xFF00) >> 8) == SIGTRAP | 0x80
}

pub fn stop_additional(status: i32) -> c_int {
Expand Down Expand Up @@ -196,7 +249,9 @@ fn decode(pid : Pid, status: i32) -> WaitStatus {
if #[cfg(any(target_os = "linux", target_os = "android"))] {
fn decode_stopped(pid: Pid, status: i32) -> WaitStatus {
let status_additional = status::stop_additional(status);
if status_additional == 0 {
if status::syscall_stop(status) {
WaitStatus::PtraceSyscall(pid)
} else if status_additional == 0 {
WaitStatus::Stopped(pid, status::stop_signal(status))
} else {
WaitStatus::PtraceEvent(pid, status::stop_signal(status), status::stop_additional(status))
Expand Down
50 changes: 50 additions & 0 deletions test/sys/test_wait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,53 @@ fn test_wait_exit() {
Err(_) => panic!("Error: Fork Failed")
}
}

#[cfg(any(target_os = "linux", target_os = "android"))]
// FIXME: qemu-user doesn't implement ptrace on most arches
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod ptrace {
use nix::sys::ptrace::*;
use nix::sys::ptrace::ptrace::*;
use nix::sys::signal::*;
use nix::sys::wait::*;
use nix::unistd::*;
use nix::unistd::ForkResult::*;
use std::{ptr, process};

fn ptrace_child() -> ! {
let _ = ptrace(PTRACE_TRACEME, Pid::from_raw(0), ptr::null_mut(), ptr::null_mut());
// As recommended by ptrace(2), raise SIGTRAP to pause the child
// until the parent is ready to continue
let _ = raise(SIGTRAP);
process::exit(0)
}

fn ptrace_parent(child: Pid) {
// Wait for the raised SIGTRAP
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, SIGTRAP)));
// We want to test a syscall stop and a PTRACE_EVENT stop
assert!(ptrace_setoptions(child, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXIT).is_ok());

// First, stop on the next system call, which will be exit()
assert!(ptrace(PTRACE_SYSCALL, child, ptr::null_mut(), ptr::null_mut()).is_ok());
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
// Then get the ptrace event for the process exiting
assert!(ptrace(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()).is_ok());
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceEvent(child, SIGTRAP, PTRACE_EVENT_EXIT)));
// Finally get the normal wait() result, now that the process has exited
assert!(ptrace(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()).is_ok());
assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0)));
}

#[test]
fn test_wait_ptrace() {
#[allow(unused_variables)]
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

match fork() {
Ok(Child) => ptrace_child(),
Ok(Parent { child }) => ptrace_parent(child),
Err(_) => panic!("Error: Fork Failed")
}
}
}