mirror of
https://github.com/facebookexperimental/reverie.git
synced 2024-11-24 12:17:50 +00:00
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:
parent
f51250f09c
commit
531faf7a34
4 changed files with 71 additions and 9 deletions
|
@ -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"] }
|
||||
|
|
|
@ -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
45
safeptrace/README.md
Normal 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"] }
|
||||
```
|
|
@ -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>> {
|
||||
|
|
Loading…
Reference in a new issue