summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeoffrey Thomas <geofft@ldpreload.com>2017-03-28 16:55:36 -0400
committerMarcin Mielniczuk <marmistrz.dev@zoho.eu>2017-07-25 09:09:52 +0200
commit903a52f602054dff1258b5ca04591f0c5edbe08b (patch)
tree4768078a792799c5a2b737ecd3d65da0da9dcf54
parent845453b3d2be1a760782b0c7eca02a1d9360f947 (diff)
downloadnix-903a52f602054dff1258b5ca04591f0c5edbe08b.zip
Add WaitStatus::PtraceSyscall for use with PTRACE_O_TRACESYSGOOD
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
-rw-r--r--CHANGELOG.md3
-rw-r--r--src/sys/wait.rs19
-rw-r--r--test/sys/test_wait.rs50
3 files changed, 70 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 817a89f4..b7edf991 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/src/sys/wait.rs b/src/sys/wait.rs
index ee0beade..7033422d 100644
--- a/src/sys/wait.rs
+++ b/src/sys/wait.rs
@@ -47,6 +47,8 @@ pub enum WaitStatus {
Stopped(Pid, Signal),
#[cfg(any(target_os = "linux", target_os = "android"))]
PtraceEvent(Pid, Signal, c_int),
+ #[cfg(any(target_os = "linux", target_os = "android"))]
+ PtraceSyscall(Pid),
Continued(Pid),
StillAlive
}
@@ -56,6 +58,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
@@ -82,7 +85,17 @@ mod status {
}
pub fn stop_signal(status: i32) -> Signal {
- 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 {
+ // 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 {
@@ -196,7 +209,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))
diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs
index 2e28d9e7..5f6c9231 100644
--- a/test/sys/test_wait.rs
+++ b/test/sys/test_wait.rs
@@ -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")
+ }
+ }
+}