reverie/reverie-examples/chaos.rs
facebook-github-bot 15d2f61411 Initial commit
fbshipit-source-id: c440d991296c92bdc5e109a11d269049e8840e94
2021-12-29 16:14:27 -08:00

180 lines
5.3 KiB
Rust

/*
* Copyright (c) Facebook, Inc. and its 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 serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicU64, Ordering};
use structopt::StructOpt;
use reverie::{
syscalls::{Displayable, Errno, Syscall},
Error, GlobalTool, Guest, Pid, Tool,
};
use reverie_util::CommonToolArguments;
/// 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, StructOpt)]
struct Args {
#[structopt(flatten)]
common_opts: CommonToolArguments,
#[structopt(flatten)]
chaos_opts: ChaosOpts,
}
#[derive(StructOpt, 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.
#[structopt(long, value_name = "N", default_value = "0")]
skip: u64,
/// If set, does not intercept `read`-like system calls and modify them.
#[structopt(long)]
no_read: bool,
/// If set, does not intercept `recv`-like system calls and modify them.
#[structopt(long)]
no_recv: bool,
/// If set, does not inject random `EINTR` errors.
#[structopt(long)]
no_interrupt: bool,
}
#[derive(Debug, Serialize, Deserialize, Default)]
struct ChaosTool {
count: AtomicU64,
}
impl Clone for ChaosTool {
fn clone(&self) -> Self {
ChaosTool {
count: AtomicU64::new(self.count.load(Ordering::SeqCst)),
}
}
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
struct ChaosToolGlobal {}
#[reverie::global_tool]
impl GlobalTool for ChaosToolGlobal {
type Config = ChaosOpts;
async fn receive_rpc(&self, _from: Pid, _request: ()) {}
}
#[reverie::tool]
impl Tool for ChaosTool {
type ThreadState = bool;
type GlobalState = ChaosToolGlobal;
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 ret = Err(Errno::ERESTARTSYS);
eprintln!(
"[pid={}, n={}] {} = {}",
guest.pid(),
count,
syscall.display(&memory),
ret.unwrap_or_else(|errno| -errno.into_raw() as i64)
);
return Ok(ret?);
} 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()
}