mirror of
https://github.com/facebookexperimental/reverie.git
synced 2025-01-23 05:06:23 +00:00
4820afb142
Summary: We want to allow precise timers with an instruction offset. While single stepping we have target and current value of the counter and once we match branches we only increment instructions Reviewed By: jasonwhite Differential Revision: D41269879 fbshipit-source-id: 46b3307249663de10607513dc59d8436ca907f78
811 lines
26 KiB
Rust
811 lines
26 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.
|
|
*/
|
|
|
|
//! Verifies precision, determinism, and cancellation of timer events and clocks.
|
|
//!
|
|
//! Syscalls are abused to communicate from the guest to the tool instructions
|
|
//! necessary to carry out the test, such as setting timers or reading clocks.
|
|
|
|
use std::sync::atomic::AtomicU64;
|
|
use std::sync::atomic::Ordering;
|
|
use std::sync::Mutex;
|
|
|
|
use reverie::syscalls::Getpid;
|
|
use reverie::syscalls::Gettid;
|
|
use reverie::syscalls::Syscall;
|
|
use reverie::syscalls::SyscallInfo;
|
|
use reverie::syscalls::Sysno;
|
|
use reverie::syscalls::Tgkill;
|
|
use reverie::Errno;
|
|
use reverie::Error;
|
|
use reverie::GlobalTool;
|
|
use reverie::Guest;
|
|
use reverie::Pid;
|
|
use reverie::Signal;
|
|
use reverie::Subscription;
|
|
use reverie::TimerSchedule;
|
|
use reverie::Tool;
|
|
use reverie_ptrace::regs::RegAccess;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
|
|
#[derive(Debug, Default)]
|
|
struct GlobalState {
|
|
num_timer_evts: AtomicU64,
|
|
num_signals: AtomicU64,
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone)]
|
|
struct LocalState;
|
|
|
|
#[derive(PartialEq, Debug, Eq, Clone, Copy, Serialize, Deserialize)]
|
|
enum IncrMsg {
|
|
Timer,
|
|
Signal,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
|
struct Config {
|
|
sub_syscalls_only: bool,
|
|
run_basic_tests: bool,
|
|
timeout_rcbs: u64,
|
|
timeout_rcbs_alternate: u64,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
|
struct ThreadClockState {
|
|
/// baseline for clock comparisons
|
|
last_tick: u64,
|
|
/// offset from baseline to assert at a timer event
|
|
timer_assertion: Option<u64>,
|
|
}
|
|
|
|
#[reverie::global_tool]
|
|
impl GlobalTool for GlobalState {
|
|
type Request = IncrMsg;
|
|
type Response = ();
|
|
type Config = Config;
|
|
|
|
async fn init_global_state(_: &Self::Config) -> Self {
|
|
GlobalState {
|
|
num_timer_evts: AtomicU64::new(0),
|
|
num_signals: AtomicU64::new(0),
|
|
}
|
|
}
|
|
|
|
async fn receive_rpc(&self, _from: Pid, msg: IncrMsg) -> Self::Response {
|
|
match msg {
|
|
IncrMsg::Timer => self.num_timer_evts.fetch_add(1, Ordering::SeqCst),
|
|
IncrMsg::Signal => self.num_signals.fetch_add(1, Ordering::SeqCst),
|
|
};
|
|
}
|
|
}
|
|
|
|
const BULK_INJECTION_COUNT: u64 = 10;
|
|
|
|
#[reverie::tool]
|
|
impl Tool for LocalState {
|
|
type GlobalState = GlobalState;
|
|
type ThreadState = ThreadClockState;
|
|
|
|
fn subscriptions(cfg: &Config) -> Subscription {
|
|
if cfg.sub_syscalls_only {
|
|
Subscription::all_syscalls()
|
|
} else {
|
|
Subscription::all()
|
|
}
|
|
}
|
|
|
|
async fn handle_thread_start<T: Guest<Self>>(&self, guest: &mut T) -> Result<(), Error> {
|
|
if guest.config().run_basic_tests {
|
|
assert_eq!(guest.read_clock().unwrap(), 0);
|
|
assert!(guest.set_timer(TimerSchedule::Rcbs(0)).is_err());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn handle_syscall_event<T: Guest<Self>>(
|
|
&self,
|
|
guest: &mut T,
|
|
syscall: Syscall,
|
|
) -> Result<i64, Error> {
|
|
let timeout = TimerSchedule::Rcbs(guest.config().timeout_rcbs);
|
|
let alt_timeout = TimerSchedule::Rcbs(guest.config().timeout_rcbs_alternate);
|
|
let (no, args) = syscall.into_parts();
|
|
match no {
|
|
Sysno::clock_getres => {
|
|
guest.set_timer_precise(timeout).unwrap();
|
|
}
|
|
Sysno::msgrcv => {
|
|
guest.set_timer_precise(alt_timeout).unwrap();
|
|
}
|
|
Sysno::timer_getoverrun => {
|
|
guest.set_timer(timeout).unwrap();
|
|
}
|
|
Sysno::fanotify_init => {
|
|
guest.set_timer_precise(timeout).unwrap();
|
|
let kill_call = raise_sigwinch(guest).await;
|
|
guest.tail_inject(kill_call).await
|
|
}
|
|
Sysno::fanotify_mark => {
|
|
guest.set_timer(timeout).unwrap();
|
|
let kill_call = raise_sigwinch(guest).await;
|
|
guest.tail_inject(kill_call).await
|
|
}
|
|
Sysno::msgctl => {
|
|
guest.set_timer_precise(timeout).unwrap();
|
|
for _ in 0..BULK_INJECTION_COUNT {
|
|
guest.inject(Getpid::new()).await.unwrap();
|
|
}
|
|
guest.tail_inject(Getpid::new()).await
|
|
}
|
|
Sysno::msgget => {
|
|
guest.set_timer(timeout).unwrap();
|
|
for _ in 0..BULK_INJECTION_COUNT {
|
|
guest.inject(Getpid::new()).await.unwrap();
|
|
}
|
|
guest.tail_inject(Getpid::new()).await
|
|
}
|
|
Sysno::clock_settime => {
|
|
let clock_value = guest.read_clock().unwrap();
|
|
let ts = guest.thread_state_mut();
|
|
ts.last_tick = clock_value;
|
|
ts.timer_assertion = None;
|
|
}
|
|
Sysno::timer_gettime => {
|
|
let clock_value = guest.read_clock().unwrap();
|
|
let ts = guest.thread_state_mut();
|
|
ts.last_tick = clock_value;
|
|
ts.timer_assertion = Some(args.arg0 as u64);
|
|
}
|
|
Sysno::clock_adjtime => assert_eq!(
|
|
guest.read_clock().unwrap(),
|
|
guest.thread_state_mut().last_tick + args.arg0 as u64
|
|
),
|
|
_ => guest.tail_inject(syscall).await,
|
|
};
|
|
Ok(0)
|
|
}
|
|
|
|
async fn handle_timer_event<T: Guest<Self>>(&self, guest: &mut T) {
|
|
guest.send_rpc(IncrMsg::Timer).await;
|
|
let clock_value = guest.read_clock().unwrap();
|
|
let ts = guest.thread_state();
|
|
if let Some(val) = ts.timer_assertion {
|
|
assert_eq!(ts.last_tick + val, clock_value);
|
|
}
|
|
}
|
|
|
|
async fn handle_signal_event<T: Guest<Self>>(
|
|
&self,
|
|
guest: &mut T,
|
|
signal: Signal,
|
|
) -> Result<Option<Signal>, Errno> {
|
|
guest.send_rpc(IncrMsg::Signal).await;
|
|
Ok(Some(signal))
|
|
}
|
|
}
|
|
|
|
async fn raise_sigwinch<T: Guest<LocalState>>(guest: &mut T) -> Tgkill {
|
|
let pid = guest.inject(Getpid::new()).await.unwrap();
|
|
let tid = guest.inject(Gettid::new()).await.unwrap();
|
|
Tgkill::new()
|
|
.with_tgid(pid as _)
|
|
.with_tid(tid as _)
|
|
.with_sig(libc::SIGWINCH)
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct InstructionTracingGlobal {
|
|
timer_events: Mutex<Vec<TimerEvent>>,
|
|
}
|
|
|
|
#[derive(PartialEq, Default, Debug, Eq, Clone, Copy, Serialize, Deserialize)]
|
|
struct TimerEvent {
|
|
rcbs: u64,
|
|
rip: reverie_ptrace::regs::Reg,
|
|
}
|
|
|
|
#[derive(Default, Clone, Serialize, Deserialize)]
|
|
struct InstructionTracingConfig {
|
|
set_timer_after_syscall: Option<(u64, Option<u64>)>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
enum InstructionTracingRequest {
|
|
Timer(TimerEvent),
|
|
}
|
|
|
|
#[reverie::global_tool]
|
|
impl GlobalTool for InstructionTracingGlobal {
|
|
type Request = InstructionTracingRequest;
|
|
type Response = ();
|
|
type Config = InstructionTracingConfig;
|
|
|
|
async fn receive_rpc(&self, _from: reverie::Tid, message: Self::Request) -> Self::Response {
|
|
match message {
|
|
InstructionTracingRequest::Timer(ev) => self.timer_events.lock().unwrap().push(ev),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone)]
|
|
struct InstructionTracing;
|
|
|
|
#[reverie::tool]
|
|
impl Tool for InstructionTracing {
|
|
type GlobalState = InstructionTracingGlobal;
|
|
type ThreadState = ThreadClockState;
|
|
|
|
async fn handle_syscall_event<T: Guest<Self>>(
|
|
&self,
|
|
guest: &mut T,
|
|
c: Syscall,
|
|
) -> Result<i64, Error> {
|
|
match guest.config().set_timer_after_syscall {
|
|
Some((rcb, Some(instr))) => {
|
|
guest.set_timer_precise(reverie::TimerSchedule::RcbsAndInstructions(rcb, instr))?;
|
|
}
|
|
|
|
Some((rcb, None)) => {
|
|
guest.set_timer_precise(reverie::TimerSchedule::Rcbs(rcb))?;
|
|
}
|
|
_ => {}
|
|
}
|
|
guest.tail_inject(c).await
|
|
}
|
|
|
|
async fn handle_timer_event<T: Guest<Self>>(&self, guest: &mut T) {
|
|
let rcbs = guest.read_clock().expect("clock available");
|
|
let rip = guest.regs().await.ip();
|
|
guest
|
|
.send_rpc(InstructionTracingRequest::Timer(TimerEvent { rcbs, rip }))
|
|
.await;
|
|
}
|
|
}
|
|
|
|
#[cfg(all(not(sanitized), test))]
|
|
mod tests {
|
|
//! These tests are highly sensitive to the number of branches executed
|
|
//! in the guest, and this must remain consistent between opt and debug
|
|
//! mode. If you pass non-constant values into do_branches and need them to
|
|
//! be exact, be sure to precompute them in the tracer before moving them
|
|
//! into the tracee, otherwise underflow or overflow checks will break the
|
|
//! tests.
|
|
|
|
use reverie_ptrace::regs::RegAccess;
|
|
use reverie_ptrace::ret_without_perf;
|
|
use reverie_ptrace::testing::check_fn;
|
|
use reverie_ptrace::testing::check_fn_with_config;
|
|
use reverie_ptrace::testing::do_branches;
|
|
use test_case::test_case;
|
|
|
|
use super::*;
|
|
|
|
#[inline(always)]
|
|
unsafe fn syscall_no_branches(no: Sysno, arg1: libc::c_long) {
|
|
syscalls::syscall!(no, arg1, 0, 0, 0, 0, 0);
|
|
}
|
|
|
|
fn sched_precise() {
|
|
unsafe { syscall_no_branches(Sysno::clock_getres, 0) }
|
|
}
|
|
|
|
fn sched_precise_alternate_rcb_count() {
|
|
unsafe { syscall_no_branches(Sysno::msgrcv, 0) }
|
|
}
|
|
|
|
fn sched_imprecise() {
|
|
unsafe { syscall_no_branches(Sysno::timer_getoverrun, 0) }
|
|
}
|
|
|
|
fn mark_clock() {
|
|
unsafe { syscall_no_branches(Sysno::clock_settime, 0) }
|
|
}
|
|
|
|
fn assert_clock(delta: u64) {
|
|
unsafe { syscall_no_branches(Sysno::clock_adjtime, delta as i64) }
|
|
}
|
|
|
|
fn assert_clock_at_next_timer(value: u64) {
|
|
unsafe { syscall_no_branches(Sysno::timer_gettime, value as i64) }
|
|
}
|
|
|
|
fn do_syscall() {
|
|
unsafe { syscall_no_branches(Sysno::clock_gettime, 0) }
|
|
}
|
|
|
|
fn immediate_exit() {
|
|
unsafe { syscall_no_branches(Sysno::exit, 0) }
|
|
}
|
|
|
|
fn sched_precise_and_raise() {
|
|
unsafe { syscall_no_branches(Sysno::fanotify_init, 0) }
|
|
}
|
|
|
|
fn sched_imprecise_and_raise() {
|
|
unsafe { syscall_no_branches(Sysno::fanotify_mark, 0) }
|
|
}
|
|
|
|
fn sched_precise_and_inject() {
|
|
unsafe { syscall_no_branches(Sysno::msgctl, 0) }
|
|
}
|
|
|
|
fn sched_imprecise_and_inject() {
|
|
unsafe { syscall_no_branches(Sysno::msgget, 0) }
|
|
}
|
|
|
|
fn cpuid() {
|
|
#[cfg(target_arch = "x86_64")]
|
|
unsafe {
|
|
core::arch::x86_64::__cpuid(0);
|
|
}
|
|
}
|
|
|
|
fn rdtsc() {
|
|
#[cfg(target_arch = "x86_64")]
|
|
unsafe {
|
|
core::arch::x86_64::_rdtsc();
|
|
}
|
|
}
|
|
|
|
fn rdtscp() {
|
|
#[cfg(target_arch = "x86_64")]
|
|
unsafe {
|
|
let mut x = 0u32;
|
|
core::arch::x86_64::__rdtscp(&mut x as *mut _);
|
|
}
|
|
}
|
|
|
|
fn ts_check_fn(rcbs: u64, f: impl FnOnce()) -> GlobalState {
|
|
use reverie_ptrace::testing::check_fn_with_config;
|
|
check_fn_with_config::<LocalState, _>(
|
|
f,
|
|
Config {
|
|
timeout_rcbs: rcbs,
|
|
..Default::default()
|
|
},
|
|
true,
|
|
)
|
|
}
|
|
|
|
const MANY_RCBS: u64 = 10000; // Normal perf signaling
|
|
const LESS_RCBS: u64 = 15; // Low enough to use artificial signaling
|
|
|
|
#[test_case(MANY_RCBS, sched_precise)]
|
|
#[test_case(MANY_RCBS, sched_imprecise)]
|
|
#[test_case(LESS_RCBS, sched_precise)]
|
|
#[test_case(LESS_RCBS, sched_imprecise)]
|
|
fn timer_delays_timer(rcbs: u64, schedule_timer: fn() -> ()) {
|
|
ret_without_perf!();
|
|
let rcbd2 = rcbs / 2;
|
|
let rcbx2 = rcbs * 2;
|
|
let gs = ts_check_fn(rcbs, move || {
|
|
schedule_timer();
|
|
do_branches(rcbd2);
|
|
schedule_timer();
|
|
do_branches(rcbd2);
|
|
schedule_timer();
|
|
do_branches(rcbd2);
|
|
schedule_timer();
|
|
do_branches(rcbx2);
|
|
});
|
|
assert_eq!(gs.num_timer_evts.into_inner(), 1);
|
|
}
|
|
|
|
#[test_case(MANY_RCBS, sched_precise)]
|
|
#[test_case(MANY_RCBS, sched_imprecise)]
|
|
#[test_case(LESS_RCBS, sched_precise)]
|
|
#[test_case(LESS_RCBS, sched_imprecise)]
|
|
fn timer_is_single(rcbs: u64, schedule_timer: fn() -> ()) {
|
|
ret_without_perf!();
|
|
let rcbx2 = rcbs * 2;
|
|
let rcbx10 = rcbs * 10;
|
|
let rcbx20 = rcbs * 20;
|
|
let gs = ts_check_fn(rcbs, move || {
|
|
schedule_timer();
|
|
do_branches(rcbx2);
|
|
schedule_timer();
|
|
do_branches(rcbx10);
|
|
schedule_timer();
|
|
do_branches(rcbx20);
|
|
});
|
|
assert_eq!(gs.num_timer_evts.into_inner(), 3);
|
|
}
|
|
|
|
#[test_case(MANY_RCBS, sched_precise)]
|
|
#[test_case(MANY_RCBS, sched_precise_and_inject)]
|
|
#[test_case(LESS_RCBS, sched_precise)]
|
|
#[test_case(LESS_RCBS, sched_precise_and_inject)]
|
|
fn precise_delivery_exact(rcbs: u64, schedule_timer: fn() -> ()) {
|
|
ret_without_perf!();
|
|
|
|
// `do_branches(n)` does n+1 branches, so `rcbs - 1` will be the
|
|
// first argument resulting in a timer event.
|
|
// Precompute to avoid underflow checks in the guest
|
|
|
|
let branch_ct = rcbs - 2;
|
|
let gs3 = ts_check_fn(rcbs, move || {
|
|
schedule_timer();
|
|
do_branches(branch_ct);
|
|
immediate_exit();
|
|
});
|
|
assert_eq!(gs3.num_timer_evts.into_inner(), 0);
|
|
|
|
let branch_ct = rcbs - 1;
|
|
let gs2 = ts_check_fn(rcbs, move || {
|
|
schedule_timer();
|
|
do_branches(branch_ct);
|
|
immediate_exit();
|
|
});
|
|
assert_eq!(gs2.num_timer_evts.into_inner(), 1);
|
|
}
|
|
|
|
fn early_stop_rcbs(rcbs: u64) -> Vec<u64> {
|
|
// Final value is 2 because do_branches adds 1
|
|
[rcbs / 2, 1000, 100, 10, 2]
|
|
.iter()
|
|
.map(|x| *x)
|
|
.filter(|x| *x < rcbs)
|
|
.collect()
|
|
}
|
|
const LET_PASS_STOP_RCBS: u64 = 1;
|
|
|
|
#[test_case(MANY_RCBS, do_syscall, sched_precise, 0)]
|
|
#[test_case(MANY_RCBS, do_syscall, sched_imprecise, 0)]
|
|
#[test_case(MANY_RCBS, cpuid, sched_precise, 0)]
|
|
#[test_case(MANY_RCBS, cpuid, sched_imprecise, 0)]
|
|
#[test_case(MANY_RCBS, rdtsc, sched_precise, 0)]
|
|
#[test_case(MANY_RCBS, rdtsc, sched_imprecise, 0)]
|
|
#[test_case(MANY_RCBS, rdtscp, sched_precise, 0)]
|
|
#[test_case(MANY_RCBS, rdtscp, sched_imprecise, 0)]
|
|
#[test_case(MANY_RCBS, sched_precise, sched_precise, 1)]
|
|
#[test_case(MANY_RCBS, sched_imprecise, sched_precise, 1)]
|
|
#[test_case(MANY_RCBS, sched_imprecise, sched_imprecise, 1)]
|
|
#[test_case(MANY_RCBS, sched_precise, sched_imprecise, 1)]
|
|
#[test_case(LESS_RCBS, do_syscall, sched_precise, 0)]
|
|
#[test_case(LESS_RCBS, do_syscall, sched_imprecise, 0)]
|
|
#[test_case(LESS_RCBS, cpuid, sched_precise, 0)]
|
|
#[test_case(LESS_RCBS, cpuid, sched_imprecise, 0)]
|
|
#[test_case(LESS_RCBS, rdtsc, sched_precise, 0)]
|
|
#[test_case(LESS_RCBS, rdtsc, sched_imprecise, 0)]
|
|
#[test_case(LESS_RCBS, rdtscp, sched_precise, 0)]
|
|
#[test_case(LESS_RCBS, rdtscp, sched_imprecise, 0)]
|
|
#[test_case(LESS_RCBS, sched_precise, sched_precise, 1)]
|
|
#[test_case(LESS_RCBS, sched_imprecise, sched_precise, 1)]
|
|
#[test_case(LESS_RCBS, sched_imprecise, sched_imprecise, 1)]
|
|
#[test_case(LESS_RCBS, sched_precise, sched_imprecise, 1)]
|
|
fn assert_cancels_timers(
|
|
rcbs: u64,
|
|
fun: fn() -> (),
|
|
schedule_timer: fn() -> (),
|
|
additional_evts: u64,
|
|
) {
|
|
ret_without_perf!();
|
|
let rcbx2 = rcbs * 2;
|
|
for e in early_stop_rcbs(rcbs) {
|
|
// Precompute to avoid underflow checks in the guest
|
|
let branch_ct = rcbs - e;
|
|
let gs = ts_check_fn(rcbs, move || {
|
|
schedule_timer();
|
|
do_branches(branch_ct);
|
|
fun();
|
|
do_branches(rcbx2);
|
|
});
|
|
assert_eq!(
|
|
gs.num_timer_evts.into_inner(),
|
|
0 + additional_evts,
|
|
"iter: {}",
|
|
e
|
|
);
|
|
}
|
|
// Imprecise events can be delayed, in which case nothing fires, so only
|
|
// test this if precise:
|
|
if schedule_timer == sched_precise {
|
|
// Precompute to avoid underflow checks in the guest
|
|
let branch_ct = rcbs - LET_PASS_STOP_RCBS;
|
|
let gs = ts_check_fn(rcbs, move || {
|
|
schedule_timer();
|
|
do_branches(branch_ct);
|
|
fun();
|
|
do_branches(rcbx2);
|
|
});
|
|
assert_eq!(gs.num_timer_evts.into_inner(), 1 + additional_evts);
|
|
}
|
|
}
|
|
|
|
#[test_case(MANY_RCBS, sched_precise, sched_precise_and_raise)]
|
|
#[test_case(MANY_RCBS, sched_imprecise, sched_imprecise_and_raise)]
|
|
#[test_case(LESS_RCBS, sched_precise, sched_precise_and_raise)]
|
|
#[test_case(LESS_RCBS, sched_imprecise, sched_imprecise_and_raise)]
|
|
fn signals_cancel_timers(
|
|
rcbs: u64,
|
|
schedule_timer: fn() -> (),
|
|
schedule_timer_and_raise: fn() -> (),
|
|
) {
|
|
ret_without_perf!();
|
|
let rcbd2 = rcbs / 2;
|
|
let rcbx2 = rcbs * 2;
|
|
|
|
// The signal after scheduling should immediately cancel the event
|
|
let gs = ts_check_fn(rcbs, move || {
|
|
schedule_timer();
|
|
do_branches(rcbd2);
|
|
schedule_timer_and_raise();
|
|
do_branches(rcbx2);
|
|
schedule_timer_and_raise();
|
|
do_branches(rcbx2);
|
|
});
|
|
assert_eq!(gs.num_signals.into_inner(), 2); // defensive
|
|
assert_eq!(gs.num_timer_evts.into_inner(), 0);
|
|
|
|
// If we don't raise, events delivered as expected:
|
|
let gs = ts_check_fn(rcbs, move || {
|
|
schedule_timer();
|
|
do_branches(rcbd2);
|
|
schedule_timer();
|
|
do_branches(rcbx2);
|
|
schedule_timer();
|
|
do_branches(rcbx2);
|
|
});
|
|
assert_eq!(gs.num_signals.into_inner(), 0); // defensive
|
|
assert_eq!(gs.num_timer_evts.into_inner(), 2);
|
|
}
|
|
|
|
#[test_case(MANY_RCBS, sched_precise)]
|
|
#[test_case(MANY_RCBS, sched_imprecise)]
|
|
#[test_case(LESS_RCBS, sched_precise)]
|
|
#[test_case(LESS_RCBS, sched_imprecise)]
|
|
fn not_subscribed_doesnt_cancel(rcbs: u64, schedule_timer: fn() -> ()) {
|
|
ret_without_perf!();
|
|
let rcbd2 = rcbs / 2;
|
|
let rcbx2 = rcbs * 2;
|
|
let gs = check_fn_with_config::<LocalState, _>(
|
|
move || {
|
|
schedule_timer();
|
|
do_branches(rcbd2);
|
|
cpuid();
|
|
rdtsc();
|
|
rdtscp();
|
|
do_branches(rcbx2);
|
|
},
|
|
Config {
|
|
timeout_rcbs: rcbs,
|
|
sub_syscalls_only: true,
|
|
..Default::default()
|
|
},
|
|
true,
|
|
);
|
|
assert_eq!(gs.num_timer_evts.into_inner(), 1);
|
|
}
|
|
|
|
fn loop_with_branch_ct(
|
|
rcbs: u64,
|
|
branch_ct: u64,
|
|
iters: u64,
|
|
schedule_timer: fn() -> (),
|
|
) -> GlobalState {
|
|
ts_check_fn(rcbs, move || {
|
|
for _ in 0..iters {
|
|
schedule_timer();
|
|
do_branches(branch_ct);
|
|
schedule_timer(); // cancel timer before loop branch
|
|
}
|
|
immediate_exit(); // RCBs in teardown would trigger the last iter's event
|
|
})
|
|
}
|
|
|
|
#[test_case(MANY_RCBS, sched_precise)]
|
|
#[test_case(MANY_RCBS, sched_precise_and_inject)]
|
|
#[test_case(LESS_RCBS, sched_precise)]
|
|
#[test_case(LESS_RCBS, sched_precise_and_inject)]
|
|
fn precise_deterministic_loop(rcbs: u64, schedule_timer: fn() -> ()) {
|
|
ret_without_perf!();
|
|
let rcbm2 = rcbs - 2;
|
|
let rcbm1 = rcbs - 1;
|
|
const ITERS: u64 = 500;
|
|
assert_eq!(
|
|
loop_with_branch_ct(rcbs, rcbm2, ITERS, schedule_timer)
|
|
.num_timer_evts
|
|
.into_inner(),
|
|
0
|
|
);
|
|
assert_eq!(
|
|
loop_with_branch_ct(rcbs, rcbm1, ITERS, schedule_timer)
|
|
.num_timer_evts
|
|
.into_inner(),
|
|
ITERS
|
|
);
|
|
}
|
|
|
|
#[test_case(MANY_RCBS, sched_imprecise)]
|
|
#[test_case(MANY_RCBS, sched_imprecise_and_inject)]
|
|
#[test_case(LESS_RCBS, sched_imprecise)]
|
|
#[test_case(LESS_RCBS, sched_imprecise_and_inject)]
|
|
fn imprecise_not_early_loop(rcbs: u64, schedule_timer: fn() -> ()) {
|
|
ret_without_perf!();
|
|
const ITERS: u64 = 2000;
|
|
let rcbm2 = rcbs - 2;
|
|
assert_eq!(
|
|
loop_with_branch_ct(rcbs, rcbm2, ITERS, schedule_timer)
|
|
.num_timer_evts
|
|
.into_inner(),
|
|
0
|
|
);
|
|
}
|
|
|
|
/// Regression test: Verify that a shorter event isn't cancelled by the
|
|
/// occurrence of longer one if the two happen to align.
|
|
#[test]
|
|
fn long_short_not_cancelled() {
|
|
ret_without_perf!();
|
|
// Doing this test correctly requires correctly predicting the amount of
|
|
// skid in the underlying RCB signal. For that reason, we run the gamut
|
|
// of possibilities in increments of 5, several times for each.
|
|
const ITERS: usize = 50;
|
|
|
|
// <============ MANY_RCBS ==============>
|
|
// <===== SKID_MARGIN ====>
|
|
// <= skid ==>
|
|
// <= overlap =>
|
|
// ----------------------------------------------------
|
|
// ^ ^ | ^ ^
|
|
// sched timeout | signal delivery
|
|
// |
|
|
// `> schedule short event to cause step overlap
|
|
// schedule time is therefore
|
|
// MANY_RCBS - (SKID_MARGIN - skid) - (LESS_RCBS / 2)
|
|
// skid_param = SKID_MARGIN - skid
|
|
for skid_param in (0u64..200).step_by(5) {
|
|
let branch_ct = MANY_RCBS - skid_param - (LESS_RCBS / 2);
|
|
for _ in 0..ITERS {
|
|
let gs = check_fn_with_config::<LocalState, _>(
|
|
move || {
|
|
sched_precise();
|
|
do_branches(branch_ct);
|
|
sched_precise_alternate_rcb_count();
|
|
do_branches(MANY_RCBS * 10);
|
|
},
|
|
Config {
|
|
timeout_rcbs: MANY_RCBS,
|
|
timeout_rcbs_alternate: LESS_RCBS,
|
|
..Default::default()
|
|
},
|
|
true,
|
|
);
|
|
assert_eq!(gs.num_timer_evts.into_inner(), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn clock_accuracy() {
|
|
ret_without_perf!();
|
|
for r in [100, 1000, 10000, 100000, 1000000] {
|
|
let rp1 = r + 1; // precompute
|
|
check_fn::<LocalState, _>(move || {
|
|
mark_clock();
|
|
do_branches(r);
|
|
assert_clock(rp1);
|
|
});
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn clock_stays_without_branch() {
|
|
ret_without_perf!();
|
|
let r = 2000;
|
|
let rp1 = r + 1; // precompute
|
|
check_fn::<LocalState, _>(move || {
|
|
mark_clock();
|
|
do_branches(r);
|
|
assert_clock(rp1);
|
|
assert_clock(rp1);
|
|
assert_clock(rp1);
|
|
assert_clock(rp1);
|
|
assert_clock(rp1);
|
|
assert_clock(rp1);
|
|
assert_clock(rp1);
|
|
assert_clock(rp1);
|
|
assert_clock(rp1);
|
|
});
|
|
}
|
|
|
|
#[test_case(MANY_RCBS, sched_precise)]
|
|
#[test_case(MANY_RCBS, sched_imprecise)]
|
|
#[test_case(LESS_RCBS, sched_precise)]
|
|
#[test_case(LESS_RCBS, sched_imprecise)]
|
|
fn clock_with_timer(rcbs: u64, schedule_timer: fn() -> ()) {
|
|
ret_without_perf!();
|
|
let a = rcbs * 2;
|
|
let b = a + 1;
|
|
let c = rcbs / 2;
|
|
let d = c + 1;
|
|
let gs = ts_check_fn(rcbs, move || {
|
|
mark_clock();
|
|
schedule_timer();
|
|
do_branches(a);
|
|
assert_clock(b); // timer received
|
|
|
|
mark_clock();
|
|
schedule_timer();
|
|
do_branches(c);
|
|
assert_clock(d); // timer outstanding
|
|
});
|
|
assert_eq!(gs.num_timer_evts.into_inner(), 1);
|
|
}
|
|
|
|
#[test_case(MANY_RCBS)]
|
|
#[test_case(LESS_RCBS)]
|
|
fn clock_at_timer_delivery(rcbs: u64) {
|
|
ret_without_perf!();
|
|
let rcbx2 = rcbs * 2;
|
|
let gs = ts_check_fn(rcbs, move || {
|
|
mark_clock();
|
|
assert_clock_at_next_timer(rcbs);
|
|
sched_precise();
|
|
do_branches(rcbx2);
|
|
});
|
|
assert_eq!(gs.num_timer_evts.into_inner(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn basic() {
|
|
ret_without_perf!();
|
|
let _gs = check_fn_with_config::<LocalState, _>(
|
|
move || {
|
|
do_syscall();
|
|
},
|
|
Config {
|
|
run_basic_tests: true,
|
|
..Default::default()
|
|
},
|
|
true,
|
|
);
|
|
}
|
|
|
|
#[test_case(1000, None, 1000, Some(1))]
|
|
#[test_case(1000, Some(1), 1000, Some(2))]
|
|
#[test_case(1000, Some(100), 1000, Some(101))]
|
|
#[test_case(10, None, 10, Some(1))]
|
|
#[test_case(10, Some(1), 10, Some(2))]
|
|
#[test_case(10, Some(100), 10, Some(101))]
|
|
fn test_set_precice_with_instructions_after_syscall(
|
|
rcb_1: u64,
|
|
instr_1: Option<u64>,
|
|
rcb_2: u64,
|
|
instr_2: Option<u64>,
|
|
) {
|
|
ret_without_perf!();
|
|
let gs_1 = check_fn_with_config::<InstructionTracing, _>(
|
|
move || {
|
|
do_syscall();
|
|
do_branches(MANY_RCBS);
|
|
},
|
|
InstructionTracingConfig {
|
|
set_timer_after_syscall: Some((rcb_1, instr_1)),
|
|
..Default::default()
|
|
},
|
|
true,
|
|
);
|
|
|
|
let gs_2 = check_fn_with_config::<InstructionTracing, _>(
|
|
move || {
|
|
do_syscall();
|
|
do_branches(MANY_RCBS);
|
|
},
|
|
InstructionTracingConfig {
|
|
set_timer_after_syscall: Some((rcb_2, instr_2)),
|
|
..Default::default()
|
|
},
|
|
true,
|
|
);
|
|
let rips = [&gs_1, &gs_2].map(|s| s.timer_events.lock().unwrap()[0].rip);
|
|
assert_ne!(rips[0], rips[1]);
|
|
}
|
|
}
|