-
Notifications
You must be signed in to change notification settings - Fork 696
ptrace support for BSDs #949
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
use errno::Errno; | ||
use libc::{self, c_int}; | ||
use std::ptr; | ||
use sys::signal::Signal; | ||
use unistd::Pid; | ||
use Result; | ||
|
||
pub type RequestType = c_int; | ||
|
||
cfg_if! { | ||
if #[cfg(any(target_os = "dragonfly", | ||
target_os = "freebsd", | ||
target_os = "macos", | ||
target_os = "openbsd"))] { | ||
#[doc(hidden)] | ||
pub type AddressType = *mut ::libc::c_char; | ||
} else { | ||
#[doc(hidden)] | ||
pub type AddressType = *mut ::libc::c_void; | ||
} | ||
} | ||
|
||
libc_enum! { | ||
#[repr(i32)] | ||
/// Ptrace Request enum defining the action to be taken. | ||
pub enum Request { | ||
Susurrus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
PT_TRACE_ME, | ||
PT_READ_I, | ||
PT_READ_D, | ||
#[cfg(target_os = "macos")] | ||
PT_READ_U, | ||
PT_WRITE_I, | ||
PT_WRITE_D, | ||
#[cfg(target_os = "macos")] | ||
PT_WRITE_U, | ||
PT_CONTINUE, | ||
PT_KILL, | ||
#[cfg(any(any(target_os = "dragonfly", | ||
target_os = "freebsd", | ||
target_os = "macos"), | ||
all(target_os = "openbsd", target_arch = "x86_64"), | ||
all(target_os = "netbsd", any(target_arch = "x86_64", | ||
target_arch = "powerpc"))))] | ||
PT_STEP, | ||
PT_ATTACH, | ||
PT_DETACH, | ||
#[cfg(target_os = "macos")] | ||
PT_SIGEXC, | ||
#[cfg(target_os = "macos")] | ||
PT_THUPDATE, | ||
#[cfg(target_os = "macos")] | ||
PT_ATTACHEXC | ||
} | ||
} | ||
|
||
unsafe fn ptrace_other( | ||
request: Request, | ||
pid: Pid, | ||
addr: AddressType, | ||
data: c_int, | ||
) -> Result<c_int> { | ||
Errno::result(libc::ptrace( | ||
request as RequestType, | ||
libc::pid_t::from(pid), | ||
addr, | ||
data, | ||
)).map(|_| 0) | ||
} | ||
|
||
/// Sets the process as traceable, as with `ptrace(PT_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(Request::PT_TRACE_ME, Pid::from_raw(0), ptr::null_mut(), 0).map(|_| ()) } | ||
} | ||
|
||
/// Attach to a running process, as with `ptrace(PT_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(Request::PT_ATTACH, pid, ptr::null_mut(), 0).map(|_| ()) } | ||
} | ||
|
||
/// Detaches the current running process, as with `ptrace(PT_DETACH, ...)` | ||
/// | ||
/// Detaches from the process specified in pid allowing it to run freely | ||
pub fn detach(pid: Pid) -> Result<()> { | ||
unsafe { ptrace_other(Request::PT_DETACH, pid, ptr::null_mut(), 0).map(|_| ()) } | ||
} | ||
|
||
/// 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 c_int, | ||
None => 0, | ||
}; | ||
unsafe { | ||
// Ignore the useless return value | ||
ptrace_other(Request::PT_CONTINUE, pid, 1 as AddressType, data).map(|_| ()) | ||
} | ||
} | ||
|
||
/// Issues a kill request as with `ptrace(PT_KILL, ...)` | ||
/// | ||
/// This request is equivalent to `ptrace(PT_CONTINUE, ..., SIGKILL);` | ||
pub fn kill(pid: Pid) -> Result<()> { | ||
unsafe { | ||
ptrace_other(Request::PT_KILL, pid, 0 as AddressType, 0).map(|_| ()) | ||
} | ||
} | ||
|
||
/// Move the stopped tracee process forward by a single step as with | ||
/// `ptrace(PT_STEP, ...)` | ||
/// | ||
/// Advances the execution of the process with PID `pid` by a single step optionally delivering a | ||
/// signal specified by `sig`. | ||
/// | ||
/// # Example | ||
/// ```rust | ||
/// extern crate nix; | ||
/// use nix::sys::ptrace::step; | ||
/// use nix::unistd::Pid; | ||
/// use nix::sys::signal::Signal; | ||
/// use nix::sys::wait::*; | ||
/// fn main() { | ||
/// // If a process changes state to the stopped state because of a SIGUSR1 | ||
/// // signal, this will step the process forward and forward the user | ||
/// // signal to the stopped process | ||
/// match waitpid(Pid::from_raw(-1), None) { | ||
/// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => { | ||
/// let _ = step(pid, Signal::SIGUSR1); | ||
/// } | ||
/// _ => {}, | ||
/// } | ||
/// } | ||
/// ``` | ||
#[cfg( | ||
any( | ||
any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"), | ||
all(target_os = "openbsd", target_arch = "x86_64"), | ||
all(target_os = "netbsd", | ||
any(target_arch = "x86_64", target_arch = "powerpc") | ||
) | ||
) | ||
)] | ||
pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { | ||
let data = match sig.into() { | ||
Some(s) => s as c_int, | ||
None => 0, | ||
}; | ||
unsafe { ptrace_other(Request::PT_STEP, pid, ptr::null_mut(), data).map(|_| ()) } | ||
} | ||
|
||
/// Reads a word from a processes memory at the given address | ||
pub fn read(pid: Pid, addr: AddressType) -> Result<c_int> { | ||
unsafe { | ||
// Traditionally there was a difference between reading data or | ||
// instruction memory but not in modern systems. | ||
ptrace_other(Request::PT_READ_D, pid, addr, 0) | ||
} | ||
} | ||
|
||
/// Writes a word into the processes memory at the given address | ||
pub fn write(pid: Pid, addr: AddressType, data: c_int) -> Result<()> { | ||
Susurrus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
unsafe { ptrace_other(Request::PT_WRITE_D, pid, addr, data).map(|_| ()) } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
///! Provides helpers for making ptrace system calls | ||
|
||
#[cfg(any(target_os = "android", target_os = "linux"))] | ||
mod linux; | ||
|
||
#[cfg(any(target_os = "android", target_os = "linux"))] | ||
pub use self::linux::*; | ||
|
||
#[cfg(any(target_os = "dragonfly", | ||
target_os = "freebsd", | ||
target_os = "macos", | ||
target_os = "netbsd", | ||
target_os = "openbsd"))] | ||
mod bsd; | ||
|
||
#[cfg(any(target_os = "dragonfly", | ||
target_os = "freebsd", | ||
target_os = "macos", | ||
target_os = "netbsd", | ||
target_os = "openbsd" | ||
))] | ||
pub use self::bsd::*; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
use nix::Error; | ||
use nix::errno::Errno; | ||
use nix::unistd::getpid; | ||
use nix::sys::ptrace::{self, Options}; | ||
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; | ||
|
@@ -11,25 +13,29 @@ 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 | ||
let err = ptrace::attach(getpid()).unwrap_err(); | ||
assert!(err == Error::Sys(Errno::EPERM) || err == Error::Sys(Errno::ENOSYS)); | ||
assert!(err == Error::Sys(Errno::EPERM) || err == Error::Sys(Errno::EINVAL) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are you allowing EINVAL? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://www.freebsd.org/cgi/man.cgi?query=ptrace the BSDs seem to use EINVAL for attempting to attach to yourself. I don't know if all BSDs do that or if some mimic linux and do EPERM so I just had it as that for now. I can investigate and make it more specific though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NetBSD returns 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() { | ||
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() { | ||
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() { | ||
if let Err(Error::UnsupportedOperation) = ptrace::getsiginfo(getpid()) { | ||
panic!("ptrace_getsiginfo returns Error::UnsupportedOperation!"); | ||
|
@@ -38,6 +44,7 @@ fn test_ptrace_getsiginfo() { | |
|
||
// 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() { | ||
let siginfo = unsafe { mem::uninitialized() }; | ||
if let Err(Error::UnsupportedOperation) = ptrace::setsiginfo(getpid(), &siginfo) { | ||
|
@@ -50,10 +57,12 @@ fn test_ptrace_setsiginfo() { | |
fn test_ptrace_cont() { | ||
use nix::sys::ptrace; | ||
use nix::sys::signal::{raise, Signal}; | ||
use nix::sys::wait::{waitpid, WaitStatus}; | ||
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; | ||
use nix::unistd::fork; | ||
use nix::unistd::ForkResult::*; | ||
|
||
let _m = ::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 | ||
|
@@ -79,9 +88,18 @@ fn test_ptrace_cont() { | |
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(); | ||
ptrace::cont(child, Some(Signal::SIGKILL)).unwrap(); | ||
match waitpid(child, None) { | ||
Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {} | ||
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"), | ||
} | ||
}, | ||
|
Uh oh!
There was an error while loading. Please reload this page.