diff options
-rw-r--r-- | CHANGELOG.md | 3 | ||||
-rw-r--r-- | src/sys/ptrace.rs | 71 | ||||
-rw-r--r-- | test/sys/test_ptrace.rs | 49 | ||||
-rw-r--r-- | test/sys/test_wait.rs | 11 |
4 files changed, 122 insertions, 12 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 31902b24..fe19e11c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +- Added specialized wrappers: `sys::ptrace::{traceme, syscall, cont, attach}`. Using the matching routines + with `sys::ptrace::ptrace` is now deprecated. - Added `nix::poll` module for all platforms ([#672](https://github.com/nix-rust/nix/pull/672)) - Added `nix::ppoll` function for FreeBSD and DragonFly @@ -17,6 +19,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Renamed existing `ptrace` wrappers to encourage namespacing ([#692](https://github.com/nix-rust/nix/pull/692)) +- Marked `sys::ptrace::ptrace` as `unsafe`. - Changed function signature of `socket()` and `socketpair()`. The `protocol` argument has changed type from `c_int` to `SockProtocol`. It accepts a `None` value for default protocol that was specified with zero using `c_int`. diff --git a/src/sys/ptrace.rs b/src/sys/ptrace.rs index 877bfcb0..8bb9c63a 100644 --- a/src/sys/ptrace.rs +++ b/src/sys/ptrace.rs @@ -1,7 +1,10 @@ +//! For detailed description of the ptrace requests, consult `man ptrace`. + use std::{mem, ptr}; use {Errno, Error, Result}; use libc::{c_void, c_long, siginfo_t}; use ::unistd::Pid; +use sys::signal::Signal; pub mod ptrace { use libc::c_int; @@ -70,7 +73,11 @@ mod ffi { /// Performs a ptrace request. If the request in question is provided by a specialised function /// this function will return an unsupported operation error. -pub fn ptrace(request: ptrace::PtraceRequest, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> { +#[deprecated( + since="0.10.0", + note="usages of `ptrace()` should be replaced with the specialized helper functions instead" +)] +pub unsafe fn ptrace(request: ptrace::PtraceRequest, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> { use self::ptrace::*; match request { @@ -103,8 +110,8 @@ fn ptrace_get_data<T>(request: ptrace::PtraceRequest, pid: Pid) -> Result<T> { Ok(data) } -fn ptrace_other(request: ptrace::PtraceRequest, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> { - Errno::result(unsafe { ffi::ptrace(request, pid.into(), addr, data) }).map(|_| 0) +unsafe fn ptrace_other(request: ptrace::PtraceRequest, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> { + Errno::result(ffi::ptrace(request, pid.into(), addr, data)).map(|_| 0) } /// Set options, as with `ptrace(PTRACE_SETOPTIONS,...)`. @@ -140,3 +147,61 @@ pub fn setsiginfo(pid: Pid, sig: &siginfo_t) -> Result<()> { Err(e) => Err(e), } } + +/// Sets the process as traceable, as with `ptrace(PTRACE_TRACEME, ...)` +/// +/// Indicates that this process is to be traced by its parent. +/// This is the only ptrace request to be issued by the tracee. +pub fn traceme() -> Result<()> { + unsafe { + ptrace_other( + ptrace::PTRACE_TRACEME, + Pid::from_raw(0), + ptr::null_mut(), + ptr::null_mut(), + ).map(|_| ()) // ignore the useless return value + } +} + +/// Ask for next syscall, as with `ptrace(PTRACE_SYSCALL, ...)` +/// +/// Arranges for the tracee to be stopped at the next entry to or exit from a system call. +pub fn syscall(pid: Pid) -> Result<()> { + unsafe { + ptrace_other( + ptrace::PTRACE_SYSCALL, + pid, + ptr::null_mut(), + ptr::null_mut(), + ).map(|_| ()) // ignore the useless return value + } +} + +/// Attach to a running process, as with `ptrace(PTRACE_ATTACH, ...)` +/// +/// Attaches to the process specified in pid, making it a tracee of the calling process. +pub fn attach(pid: Pid) -> Result<()> { + unsafe { + ptrace_other( + ptrace::PTRACE_ATTACH, + pid, + ptr::null_mut(), + ptr::null_mut(), + ).map(|_| ()) // ignore the useless return value + } +} + +/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)` +/// +/// Continues the execution of the process with PID `pid`, optionally +/// delivering a signal specified by `sig`. +pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(ptrace::PTRACE_CONT, pid, ptr::null_mut(), data).map(|_| ()) // ignore the useless return value + } +} + diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs index 0614c13f..16b24110 100644 --- a/test/sys/test_ptrace.rs +++ b/test/sys/test_ptrace.rs @@ -3,14 +3,13 @@ use nix::errno::Errno; use nix::unistd::getpid; use nix::sys::ptrace; -use std::{mem, ptr}; +use std::mem; #[test] fn test_ptrace() { - use nix::sys::ptrace::ptrace::PTRACE_ATTACH; // Just make sure ptrace can be called at all, for now. // FIXME: qemu-user doesn't implement ptrace on all arches, so permit ENOSYS - let err = ptrace::ptrace(PTRACE_ATTACH, getpid(), ptr::null_mut(), ptr::null_mut()).unwrap_err(); + let err = ptrace::attach(getpid()).unwrap_err(); assert!(err == Error::Sys(Errno::EPERM) || err == Error::Sys(Errno::ENOSYS)); } @@ -47,3 +46,47 @@ fn test_ptrace_setsiginfo() { _ => (), } } + + +#[test] +fn test_ptrace_cont() { + use nix::sys::ptrace; + use nix::sys::signal::{raise, Signal}; + use nix::sys::wait::{waitpid, WaitStatus}; + use nix::unistd::fork; + use nix::unistd::ForkResult::*; + + // FIXME: qemu-user doesn't implement ptrace on all architectures + // and retunrs ENOSYS in this case. + // We (ab)use this behavior to detect the affected platforms + // and skip the test then. + // On valid platforms the ptrace call should return Errno::EPERM, this + // is already tested by `test_ptrace`. + let err = ptrace::attach(getpid()).unwrap_err(); + if err == Error::Sys(Errno::ENOSYS) { + return; + } + + match fork() { + Ok(Child) => { + ptrace::traceme().unwrap(); + // As recommended by ptrace(2), raise SIGTRAP to pause the child + // until the parent is ready to continue + loop { + raise(Signal::SIGTRAP).unwrap(); + } + + }, + Ok(Parent { child }) => { + assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))); + ptrace::cont(child, None).unwrap(); + assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))); + ptrace::cont(child, Signal::SIGKILL).unwrap(); + match waitpid(child, None) { + Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {} + _ => panic!("The process should have been killed"), + } + }, + Err(_) => panic!("Error: Fork Failed") + } +} diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs index 620a4e33..0193e262 100644 --- a/test/sys/test_wait.rs +++ b/test/sys/test_wait.rs @@ -60,14 +60,13 @@ mod ptrace { use nix::sys::wait::*; use nix::unistd::*; use nix::unistd::ForkResult::*; - use std::ptr; use libc::_exit; fn ptrace_child() -> ! { - ptrace::ptrace(PTRACE_TRACEME, Pid::from_raw(0), ptr::null_mut(), ptr::null_mut()).unwrap(); + ptrace::traceme().unwrap(); // As recommended by ptrace(2), raise SIGTRAP to pause the child // until the parent is ready to continue - let _ = raise(SIGTRAP); + raise(SIGTRAP).unwrap(); unsafe { _exit(0) } } @@ -78,13 +77,13 @@ mod ptrace { 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(PTRACE_SYSCALL, child, ptr::null_mut(), ptr::null_mut()).is_ok()); + assert!(ptrace::syscall(child).is_ok()); assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child))); // Then get the ptrace event for the process exiting - assert!(ptrace::ptrace(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()).is_ok()); + assert!(ptrace::cont(child, None).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(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()).is_ok()); + assert!(ptrace::cont(child, None).is_ok()); assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0))); } |