blob: 46ea3d6a14b3a9dfc1b7bf44a6c4388bd56c8d61 [file] [log] [blame] [edit]
use nix::Error;
use nix::errno::Errno;
use nix::unistd::getpid;
use nix::sys::ptrace;
#[cfg(any(target_os = "android", target_os = "linux"))]
use nix::sys::ptrace::Options;
#[cfg(any(target_os = "android", target_os = "linux"))]
use std::mem;
#[test]
fn test_ptrace() {
// Just make sure ptrace can be called at all, for now.
// FIXME: qemu-user doesn't implement ptrace on all arches, so permit ENOSYS
require_capability!(CAP_SYS_PTRACE);
let err = ptrace::attach(getpid()).unwrap_err();
assert!(err == Error::Sys(Errno::EPERM) || err == Error::Sys(Errno::EINVAL) ||
err == Error::Sys(Errno::ENOSYS));
}
// Just make sure ptrace_setoptions can be called at all, for now.
#[test]
#[cfg(any(target_os = "android", target_os = "linux"))]
fn test_ptrace_setoptions() {
require_capability!(CAP_SYS_PTRACE);
let err = ptrace::setoptions(getpid(), Options::PTRACE_O_TRACESYSGOOD).unwrap_err();
assert!(err != Error::UnsupportedOperation);
}
// Just make sure ptrace_getevent can be called at all, for now.
#[test]
#[cfg(any(target_os = "android", target_os = "linux"))]
fn test_ptrace_getevent() {
require_capability!(CAP_SYS_PTRACE);
let err = ptrace::getevent(getpid()).unwrap_err();
assert!(err != Error::UnsupportedOperation);
}
// Just make sure ptrace_getsiginfo can be called at all, for now.
#[test]
#[cfg(any(target_os = "android", target_os = "linux"))]
fn test_ptrace_getsiginfo() {
require_capability!(CAP_SYS_PTRACE);
if let Err(Error::UnsupportedOperation) = ptrace::getsiginfo(getpid()) {
panic!("ptrace_getsiginfo returns Error::UnsupportedOperation!");
}
}
// Just make sure ptrace_setsiginfo can be called at all, for now.
#[test]
#[cfg(any(target_os = "android", target_os = "linux"))]
fn test_ptrace_setsiginfo() {
require_capability!(CAP_SYS_PTRACE);
let siginfo = unsafe { mem::zeroed() };
if let Err(Error::UnsupportedOperation) = ptrace::setsiginfo(getpid(), &siginfo) {
panic!("ptrace_setsiginfo returns Error::UnsupportedOperation!");
}
}
#[test]
fn test_ptrace_cont() {
use nix::sys::ptrace;
use nix::sys::signal::{raise, Signal};
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
use nix::unistd::fork;
use nix::unistd::ForkResult::*;
require_capability!(CAP_SYS_PTRACE);
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
// 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().expect("Error: Fork Failed") {
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();
}
},
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, Some(Signal::SIGKILL)).unwrap();
match waitpid(child, None) {
Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {
// FIXME It's been observed on some systems (apple) the
// tracee may not be killed but remain as a zombie process
// affecting other wait based tests. Add an extra kill just
// to make sure there are no zombies.
let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
}
}
_ => panic!("The process should have been killed"),
}
},
}
}
// ptrace::{setoptions, getregs} are only available in these platforms
#[cfg(all(target_os = "linux",
any(target_arch = "x86_64",
target_arch = "x86"),
target_env = "gnu"))]
#[test]
fn test_ptrace_syscall() {
use nix::sys::signal::kill;
use nix::sys::ptrace;
use nix::sys::signal::Signal;
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::fork;
use nix::unistd::getpid;
use nix::unistd::ForkResult::*;
require_capability!(CAP_SYS_PTRACE);
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
match fork().expect("Error: Fork Failed") {
Child => {
ptrace::traceme().unwrap();
// first sigstop until parent is ready to continue
let pid = getpid();
kill(pid, Signal::SIGSTOP).unwrap();
kill(pid, Signal::SIGTERM).unwrap();
unsafe { ::libc::_exit(0); }
},
Parent { child } => {
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGSTOP)));
// set this option to recognize syscall-stops
ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD).unwrap();
#[cfg(target_pointer_width = "64")]
let get_syscall_id = || ptrace::getregs(child).unwrap().orig_rax as i64;
#[cfg(target_pointer_width = "32")]
let get_syscall_id = || ptrace::getregs(child).unwrap().orig_eax as i32;
// kill entry
ptrace::syscall(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
assert_eq!(get_syscall_id(), ::libc::SYS_kill);
// kill exit
ptrace::syscall(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
assert_eq!(get_syscall_id(), ::libc::SYS_kill);
// receive signal
ptrace::syscall(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTERM)));
// inject signal
ptrace::syscall(child, Signal::SIGTERM).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false)));
},
}
}