Prepare safeptrace to be published as a crate

Summary:
* Adds a `README.md` file.
 * Adds optional features to enable memory access and the async API.

`safeptrace` depends on `reverie-process` and `reverie-memory`, so those will need to be published as crates too.

Reviewed By: VladimirMakaev

Differential Revision: D44386559

fbshipit-source-id: e74c55d5d26239aa09aedac130dca880eb5f1206
This commit is contained in:
Jason White 2023-03-25 16:12:24 -07:00 committed by Facebook GitHub Bot
parent f51250f09c
commit 531faf7a34
4 changed files with 71 additions and 9 deletions

View file

@ -24,7 +24,7 @@ perf-event-open-sys = "4.0"
procfs = "0.9"
raw-cpuid = "10.6.0"
reverie = { version = "0.1.0", path = "../reverie" }
safeptrace = { version = "0.1.0", path = "../safeptrace" }
safeptrace = { version = "0.1.0", path = "../safeptrace", features = ["memory", "notifier"] }
serde = { version = "1.0.136", features = ["derive", "rc"] }
thiserror = "1.0.36"
tokio = { version = "1.25.0", features = ["full", "test-util", "tracing"] }

View file

@ -14,7 +14,7 @@ lazy_static = "1.4"
libc = "0.2.139"
nix = "0.25"
parking_lot = { version = "0.11.2", features = ["send_guard"] }
reverie-memory = { version = "0.1.0", path = "../reverie-memory" }
reverie-memory = { version = "0.1.0", path = "../reverie-memory", optional = true }
reverie-process = { version = "0.1.0", path = "../reverie-process" }
syscalls = { version = "0.6.7", features = ["serde"] }
thiserror = "1.0.36"
@ -23,3 +23,8 @@ thiserror = "1.0.36"
quickcheck = "1.0"
quickcheck_macros = "1.0"
tokio = { version = "1.25.0", features = ["full", "test-util", "tracing"] }
[features]
default = []
memory = ["reverie-memory"]
notifier = []

45
safeptrace/README.md Normal file
View file

@ -0,0 +1,45 @@
# A safe ptrace interface
This crate provides a safe and Rustic alternative to the infamous `ptrace` API.
There are many extremely subtle aspects of the raw ptrace API and this crate
helps avoid common pitfalls.
Note that this library is still rather low-level and does not claim to solve all
your ptrace problems. You have been warned!
## Features
* Ergonomic interface that provides a state machine for ptrace states. This
avoids the infamous `ESRCH` errors that can happen when you use the ptrace
API incorrectly.
* Provides an interface to read/write guest's memory (see "memory" feature
flag).
* Provides an optional and semi-experimental async interface, which can be used
with `tokio` (see "notifier" feature).
## Usage
Add this to your `Cargo.toml` file:
```
safeptrace = "0.1"
```
## Feature Flags
### `"memory"` (off by default)
Provides access to the guest's memory. Memory can only be safely accessed when
the guest is in a stopped state, thus the `MemoryAccess` trait is only
implemented for the `Stopped` type.
### `"notifier"` (off by default)
Provides an async interface for ptrace using notifier threads. This is
semi-experimental, but testing shows that it has very good performance. It works
by spawning a separate thread for each thread being traced, waiting for ptrace
events in a loop. Thus, there will be 1 thread per guest thread.
Use with:
```
safeptrace = { version = "0.1", features = ["async"] }
```

View file

@ -12,8 +12,9 @@
//! A safe ptrace API. This API forces correct usage of ptrace in that it is
//! not possible to call ptrace on a process not in a stopped state.
#[cfg(feature = "memory")]
mod memory;
#[allow(unused)]
#[cfg(feature = "notifier")]
mod notifier;
mod regs;
mod waitid;
@ -473,11 +474,12 @@ impl Stopped {
self.map_err(Errno::new(err as i32))
}
/// Waits for the next exit stop to occur. This is received asynchronously
/// regardless of what the process was doing at the time. This is useful for
/// canceling futures when a process enters a `PTRACE_EVENT_EXIT` (such as
/// when one thread calls `exit_group` and causes all other threads to
/// suddenly exit).
/// Returns a future that is notified when the next exit stop occurs. This
/// is received asynchronously regardless of what the process was doing at
/// the time. This is useful for canceling futures when a process enters a
/// `PTRACE_EVENT_EXIT` (such as when one thread calls `exit_group` and
/// causes all other threads to suddenly exit).
#[cfg(feature = "notifier")]
pub fn exit_event(&self) -> notifier::ExitFuture {
notifier::ExitFuture(self.0)
}
@ -489,7 +491,6 @@ impl Stopped {
/// pid really is in a stopped state. It is better to arrive at a stopped
/// state via other methods such as `Running::wait`.
pub fn new_unchecked(pid: Pid) -> Self {
// FIXME: Remove this method.
Stopped(pid)
}
@ -860,6 +861,7 @@ impl Running {
/// Like `wait`, but filters out events we don't care about by resuming the
/// tracee when encountering them. This is useful for skipping past spurious
/// events until a point we know the tracee must stop.
#[cfg(feature = "notifier")]
pub async fn wait_until<F>(mut self, mut pred: F) -> Result<Wait, Error>
where
F: FnMut(&Event) -> bool,
@ -882,6 +884,7 @@ impl Running {
/// Waits until we receive a specific stop signal. Useful for skipping past
/// spurious signals.
#[cfg(feature = "notifier")]
pub async fn wait_for_signal(self, sig: Signal) -> Result<Wait, Error> {
self.wait_until(|event| event == &Event::Signal(sig)).await
}
@ -891,11 +894,18 @@ impl Running {
/// canceling futures when a process enters a `PTRACE_EVENT_EXIT` (such as
/// when one thread calls `exit_group` and causes all other threads to
/// suddenly exit).
#[cfg(feature = "notifier")]
pub fn exit_event(&self) -> notifier::ExitFuture {
notifier::ExitFuture(self.0)
}
/// Like `wait`, but wait asynchronously for the next state change.
///
/// NOTE: This call should not be mixed with [`Running::wait`]!! Once
/// [`Running::next_state`] is called once, [`Running::wait`] should never
/// be called again for that PID. This is because a notifier thread takes
/// over and calls `wait` in a continuous loop.
#[cfg(feature = "notifier")]
pub async fn next_state(self) -> Result<Wait, Error> {
notifier::WaitFuture(self).await
}
@ -918,6 +928,7 @@ impl Zombie {
}
/// Reaps the zombie by waiting for it to fully exit.
#[cfg(feature = "notifier")]
pub async fn reap(self) -> ExitStatus {
// The tracee may not be fully dead yet. It is still possible for it to
// still enter an `Event::Exit` state by waiting on it. For more info,
@ -1260,6 +1271,7 @@ mod test {
Ok(())
}
#[cfg(feature = "notifier")]
#[cfg(not(sanitized))]
#[tokio::test]
async fn notifier_basic() -> Result<(), Box<dyn std::error::Error + 'static>> {