Skip to content

Commit 8c07591

Browse files
committed
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
1 parent 07e6c2f commit 8c07591

File tree

3 files changed

+74
-2
lines changed

3 files changed

+74
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2424
and nix::Error::UnsupportedOperation}`
2525
([#614](https://github.com/nix-rust/nix/pull/614))
2626
- Added `cfmakeraw`, `cfsetspeed`, and `tcgetsid`. ([#527](https://github.com/nix-rust/nix/pull/527))
27+
- On Linux, added support for receiving `PTRACE_O_TRACESYSGOOD`
28+
events from `wait` and `waitpid` with a new `PtraceSyscall`
29+
enum variant in `nix::sys::wait::WaitStatus`
30+
([#566](https://github.com/nix-rust/nix/pull/566)).
2731

2832
### Changed
2933
- Changed `ioctl!(write ...)` to take argument by value instead as pointer.

src/sys/wait.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ pub enum WaitStatus {
4747
Stopped(Pid, Signal),
4848
#[cfg(any(target_os = "linux", target_os = "android"))]
4949
PtraceEvent(Pid, Signal, c_int),
50+
#[cfg(any(target_os = "linux", target_os = "android"))]
51+
PtraceSyscall(Pid),
5052
Continued(Pid),
5153
StillAlive
5254
}
@@ -56,6 +58,7 @@ pub enum WaitStatus {
5658
mod status {
5759
use sys::signal::Signal;
5860
use libc::c_int;
61+
use libc::SIGTRAP;
5962

6063
pub fn exited(status: i32) -> bool {
6164
(status & 0x7F) == 0
@@ -82,7 +85,17 @@ mod status {
8285
}
8386

8487
pub fn stop_signal(status: i32) -> Signal {
85-
Signal::from_c_int((status & 0xFF00) >> 8).unwrap()
88+
// Keep only 7 bits of the signal: the high bit
89+
// is used to indicate syscall stops, below.
90+
Signal::from_c_int((status & 0x7F00) >> 8).unwrap()
91+
}
92+
93+
pub fn syscall_stop(status: i32) -> bool {
94+
// From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
95+
// of delivering SIGTRAP | 0x80 as the signal number for syscall
96+
// stops. This allows easily distinguishing syscall stops from
97+
// genuine SIGTRAP signals.
98+
((status & 0xFF00) >> 8) == SIGTRAP | 0x80
8699
}
87100

88101
pub fn stop_additional(status: i32) -> c_int {
@@ -196,7 +209,9 @@ fn decode(pid : Pid, status: i32) -> WaitStatus {
196209
if #[cfg(any(target_os = "linux", target_os = "android"))] {
197210
fn decode_stopped(pid: Pid, status: i32) -> WaitStatus {
198211
let status_additional = status::stop_additional(status);
199-
if status_additional == 0 {
212+
if status::syscall_stop(status) {
213+
WaitStatus::PtraceSyscall(pid)
214+
} else if status_additional == 0 {
200215
WaitStatus::Stopped(pid, status::stop_signal(status))
201216
} else {
202217
WaitStatus::PtraceEvent(pid, status::stop_signal(status), status::stop_additional(status))

test/sys/test_wait.rs

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

0 commit comments

Comments
 (0)