reverie/reverie-examples/chaos.rs
Cameron Pickett 476ec158d6
Some checks are pending
ci / Check (push) Waiting to run
ci / Test Suite (push) Waiting to run
ci / Clippy (push) Waiting to run
Revert D65324129: update platform010 & platform010-aarch64 symlinks
Differential Revision:
D65324129

Original commit changeset: 8266029f01dc

Original Phabricator Diff: D65324129

fbshipit-source-id: b4f909750fa65b4f549a2d638743896c28e988e4
2024-11-13 16:05:04 -08:00

187 lines
5.4 KiB
Rust

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use clap::Parser;
use reverie::syscalls::Displayable;
use reverie::syscalls::Errno;
use reverie::syscalls::Syscall;
use reverie::Error;
use reverie::GlobalTool;
use reverie::Guest;
use reverie::Pid;
use reverie::Tool;
use reverie_util::CommonToolArguments;
use serde::Deserialize;
use serde::Serialize;
/// A tool to introduce inject "chaos" into a running process. A pathological
/// kernel is simulated by forcing reads to only return one byte a time.
#[derive(Debug, Parser)]
struct Args {
#[clap(flatten)]
common_opts: CommonToolArguments,
#[clap(flatten)]
chaos_opts: ChaosOpts,
}
#[derive(Parser, Debug, Serialize, Deserialize, Clone, Default)]
struct ChaosOpts {
/// Skips the first N syscalls of a process before doing any intervention.
/// This is useful when you need to skip past an error caused by the tool.
#[clap(long, value_name = "N", default_value = "0")]
skip: u64,
/// If set, does not intercept `read`-like system calls and modify them.
#[clap(long)]
no_read: bool,
/// If set, does not intercept `recv`-like system calls and modify them.
#[clap(long)]
no_recv: bool,
/// If set, does not inject random `EINTR` errors.
#[clap(long)]
no_interrupt: bool,
}
#[derive(Debug, Default)]
struct ChaosTool {
count: AtomicU64,
}
impl Clone for ChaosTool {
fn clone(&self) -> Self {
ChaosTool {
count: AtomicU64::new(self.count.load(Ordering::SeqCst)),
}
}
}
#[derive(Debug, Default, Clone)]
struct ChaosToolGlobal {}
#[reverie::global_tool]
impl GlobalTool for ChaosToolGlobal {
type Request = ();
type Response = ();
type Config = ChaosOpts;
async fn receive_rpc(&self, _from: Pid, _request: ()) {}
}
#[reverie::tool]
impl Tool for ChaosTool {
type GlobalState = ChaosToolGlobal;
type ThreadState = bool;
fn new(_pid: Pid, _cfg: &ChaosOpts) -> Self {
Self {
count: AtomicU64::new(0),
}
}
async fn handle_syscall_event<T: Guest<Self>>(
&self,
guest: &mut T,
syscall: Syscall,
) -> Result<i64, Error> {
let count = self.count.fetch_add(1, Ordering::SeqCst);
let config = guest.config().clone();
let memory = guest.memory();
// This provides a way to wait until the dynamic linker has done its job
// before we start trying to create chaos. glibc's dynamic linker has a
// bug where it doesn't retry `read` calls that don't return the
// expected amount of data.
if count < config.skip {
eprintln!(
"SKIPPED [pid={}, n={}] {}",
guest.pid(),
count,
syscall.display(&memory),
);
return guest.tail_inject(syscall).await;
}
// Transform the syscall arguments.
let syscall = match syscall {
Syscall::Read(read) => {
if !config.no_interrupt && !*guest.thread_state() {
// Return an EINTR instead of running the syscall.
// Programs should always retry the read in this case.
*guest.thread_state_mut() = true;
// XXX: inject a signal like SIGINT?
let err = Errno::ERESTARTSYS;
eprintln!(
"[pid={}, n={}] {} = {}",
guest.pid(),
count,
syscall.display(&memory),
-err.into_raw() as i64
);
return Ok(Err(err)?);
} else if !config.no_read {
// Reduce read length to 1 byte at most.
Syscall::Read(read.with_len(1.min(read.len())))
} else {
// Return syscall unmodified.
Syscall::Read(read)
}
}
Syscall::Recvfrom(recv) if !config.no_recv => {
// Reduce recv length to 1 byte at most.
Syscall::Recvfrom(recv.with_len(1.min(recv.len())))
}
x => {
eprintln!(
"[pid={}, n={}] {}",
guest.pid(),
count,
syscall.display(&memory),
);
return guest.tail_inject(x).await;
}
};
*guest.thread_state_mut() = false;
let ret = guest.inject(syscall).await;
eprintln!(
"[pid={}, n={}] {} = {}",
guest.pid(),
count,
syscall.display_with_outputs(&memory),
ret.unwrap_or_else(|errno| -errno.into_raw() as i64)
);
Ok(ret?)
}
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let args = Args::from_args();
let log_guard = args.common_opts.init_tracing();
let tracer = reverie_ptrace::TracerBuilder::<ChaosTool>::new(args.common_opts.into())
.config(args.chaos_opts)
.spawn()
.await?;
let (status, _) = tracer.wait().await?;
drop(log_guard); // Flush logs before exiting.
status.raise_or_exit()
}