Skip to content

Commit fea6ef5

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 nix-rust#550
1 parent 274b09e commit fea6ef5

File tree

3 files changed

+68
-2
lines changed

3 files changed

+68
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2121
- Added `nix::ptrace::{ptrace_get_data, ptrace_getsiginfo, ptrace_setsiginfo
2222
and nix::Error::UnsupportedOperation}`
2323
([#614](https://github.com/nix-rust/nix/pull/614))
24+
- Added a `PtraceSyscall` variant to `nix::sys::wait::WaitStatus`
25+
to support `PTRACE_O_TRACESYSGOOD` events on Linux
26+
([#566](https://github.com/nix-rust/nix/pull/566)).
2427

2528
### Changed
2629
- Marked `sys::mman::{ mmap, munmap, madvise, munlock, msync }` as unsafe.

src/sys/wait.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub enum WaitStatus {
4646
Stopped(pid_t, Signal),
4747
#[cfg(any(target_os = "linux", target_os = "android"))]
4848
PtraceEvent(pid_t, Signal, c_int),
49+
#[cfg(any(target_os = "linux", target_os = "android"))]
50+
PtraceSyscall(pid_t),
4951
Continued(pid_t),
5052
StillAlive
5153
}
@@ -55,6 +57,7 @@ pub enum WaitStatus {
5557
mod status {
5658
use sys::signal::Signal;
5759
use libc::c_int;
60+
use libc::SIGTRAP;
5861

5962
pub fn exited(status: i32) -> bool {
6063
(status & 0x7F) == 0
@@ -81,7 +84,17 @@ mod status {
8184
}
8285

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

87100
pub fn stop_additional(status: i32) -> c_int {
@@ -195,7 +208,9 @@ fn decode(pid : pid_t, status: i32) -> WaitStatus {
195208
if #[cfg(any(target_os = "linux", target_os = "android"))] {
196209
fn decode_stopped(pid: pid_t, status: i32) -> WaitStatus {
197210
let status_additional = status::stop_additional(status);
198-
if status_additional == 0 {
211+
if status::syscall_stop(status) {
212+
WaitStatus::PtraceSyscall(pid)
213+
} else if status_additional == 0 {
199214
WaitStatus::Stopped(pid, status::stop_signal(status))
200215
} else {
201216
WaitStatus::PtraceEvent(pid, status::stop_signal(status), status::stop_additional(status))

test/sys/test_wait.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,51 @@ fn test_wait_exit() {
2828
Err(_) => panic!("Error: Fork Failed")
2929
}
3030
}
31+
32+
#[cfg(any(target_os = "linux", target_os = "android"))]
33+
mod ptrace {
34+
use nix::Result;
35+
use nix::sys::ptrace::*;
36+
use nix::sys::ptrace::ptrace::*;
37+
use nix::sys::signal::*;
38+
use nix::sys::wait::*;
39+
use nix::unistd::*;
40+
use nix::unistd::ForkResult::*;
41+
use libc::{exit, pid_t};
42+
use std::ptr;
43+
44+
fn ptrace_child() -> Result<()> {
45+
try!(ptrace(PTRACE_TRACEME, 0, ptr::null_mut(), ptr::null_mut()));
46+
// As recommended by ptrace(2), raise SIGTRAP to pause the child
47+
// until the parent is ready to continue
48+
try!(raise(SIGTRAP));
49+
unsafe {exit(0)}
50+
}
51+
52+
fn ptrace_parent(child: pid_t) -> Result<()> {
53+
// Wait for the raised SIGTRAP
54+
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, SIGTRAP)));
55+
// We want to test a syscall stop and a PTRACE_EVENT stop
56+
try!(ptrace_setoptions(child, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXIT));
57+
58+
// First, stop on the next system call, which will be exit()
59+
try!(ptrace(PTRACE_SYSCALL, child, ptr::null_mut(), ptr::null_mut()));
60+
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
61+
// Then get the ptrace event for the process exiting
62+
try!(ptrace(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()));
63+
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceEvent(child, SIGTRAP, PTRACE_EVENT_EXIT)));
64+
// Finally get the normal wait() result, now that the process has exited
65+
try!(ptrace(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()));
66+
assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0)));
67+
Ok(())
68+
}
69+
70+
#[test]
71+
fn test_wait_ptrace() {
72+
match fork() {
73+
Ok(Child) => ptrace_child().unwrap(),
74+
Ok(Parent { child }) => ptrace_parent(child).unwrap(),
75+
Err(_) => panic!("Error: Fork Failed")
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)