//! Stage 19: the vDSO. Every process gets a little shared library from the //! kernel (vdso/vdso.c) plus a read-only "vvar" page with the clock's //! numbers, so musl's clock_gettime/gettimeofday/time/sched_getcpu don't //! need a system call. The detailed checks run in user/vdsotest.c (a musl //! program; exit code = failed checks); here we check the kernel side and //! use `--trace` to prove no clock syscall happens. #![no_std] #![no_main] #![feature(custom_test_frameworks)] #![test_runner(rustbelt::test_runner)] #![reexport_test_harness_main = "test_main"] extern crate alloc; use alloc::string::String; use bootloader::{BootInfo, entry_point}; use core::panic::PanicInfo; use rustbelt::process::{self, ExitStatus, SpawnArgs}; use rustbelt::{fs, memory, syscall, thread, time, vdso}; entry_point!(main); fn main(boot_info: &'static BootInfo) -> ! { rustbelt::init(); rustbelt::init_memory(boot_info); rustbelt::init_smp(); // calibrates the TSC, which turns the fast path on fs::init(); rustbelt::init_storage(); test_main(); rustbelt::hlt_loop(); } fn run(args: &[&str], trace: bool) -> ExitStatus { let mut spawn = SpawnArgs::kernel("vdsotest"); spawn.args.extend(args.iter().map(|a| String::from(*a))); spawn.trace = trace; let p = process::spawn_path("/mnt/disk0/bin/vdsotest", spawn).expect("spawn failed"); process::wait(&p) } fn counts() -> (usize, usize, usize, usize) { thread::sleep_ticks(3); (thread::thread_count(), process::count(), memory::frames_in_use(), memory::shared_frame_count()) } /// The embedded image is a one-page ELF shared object with the dynamic /// symbol and SysV hash tables musl looks for. #[test_case] fn image_is_a_small_shared_object() { let image = vdso::IMAGE; assert_eq!(&image[..4], b"\x7fELF"); assert_eq!(u16::from_le_bytes([image[16], image[17]]), 3, "not ET_DYN"); assert_eq!(vdso::image_pages(), 1); let has = |needle: &[u8]| image.windows(needle.len()).any(|w| w == needle); for name in ["__vdso_clock_gettime", "__vdso_gettimeofday", "__vdso_time", "__vdso_getcpu", "LINUX_2.6"] { assert!(has(name.as_bytes()), "{} missing from the vDSO", name); } } /// The vvar page is published (even sequence number) and, after TSC /// calibration on QEMU, allows the fast path. #[test_case] fn vvar_page_is_published() { assert_eq!(vdso::seq() % 2, 0); assert!(vdso::seq() >= 4, "published at boot and after calibration"); assert!(vdso::tsc_trusted()); assert_ne!(vdso::flags() & vdso::VVAR_TSC_OK, 0); } /// The kernel's clocks in nanoseconds: monotonic, and REALTIME - MONOTONIC /// is the boot time (constant). #[test_case] fn kernel_clock_in_nanoseconds() { let mut prev = time::now_ns(); for _ in 0..1000 { let now = time::now_ns(); assert!(now >= prev); prev = now; } let (_, mult, boot) = time::clock_params(); assert_ne!(mult, 0); let (r, m) = (time::realtime_ns(), time::now_ns()); assert!(r >= boot + m - 1_000_000 && r <= boot + m, "realtime {} vs boot {} + now {}", r, boot, m); assert!(boot / 1_000_000_000 > 1_577_836_800, "boot time before 2020"); } /// All of user/vdsotest.c: vDSO vs syscall agreement, monotonicity, other /// clocks, gettimeofday/time/getcpu, auxv, the read-only vvar page, fork, /// execve, ENOEXEC for a dynamic program. Nothing leaks. #[test_case] fn vdsotest_passes() { assert_eq!(run(&[], false), ExitStatus::Exited(0), "vdsotest failed (warm-up)"); let before = counts(); assert_eq!(run(&[], false), ExitStatus::Exited(0), "vdsotest failed"); assert_eq!(counts(), before, "vdsotest leaked something"); } fn clock_syscalls(trace: &str) -> usize { trace .lines() .filter(|l| ["] clock_gettime(", "] gettimeofday(", "] time(", "] getcpu("].iter().any(|c| l.contains(c))) .count() } /// 1000 rounds of clock_gettime(MONOTONIC and REALTIME), gettimeofday, /// time and sched_getcpu under `--trace`: not one of them is a syscall. #[test_case] fn clock_reads_make_no_syscalls() { syscall::collect_trace(true); let status = run(&["calls"], true); let trace = syscall::collect_trace(false); assert_eq!(status, ExitStatus::Exited(0)); assert!(trace.contains("exit_group(0)") || trace.contains("exit(0)"), "no trace collected:\n{}", trace); assert_eq!(clock_syscalls(&trace), 0, "clock syscalls in the trace:\n{}", trace); } /// Without a trustworthy TSC the vDSO makes the system calls instead, and /// everything still passes. #[test_case] fn falls_back_to_syscalls() { vdso::force_syscall(true); assert_eq!(vdso::flags(), 0); syscall::collect_trace(true); let calls = run(&["calls"], true); let trace = syscall::collect_trace(false); let full = run(&[], false); vdso::force_syscall(false); assert_eq!(calls, ExitStatus::Exited(0)); assert_eq!(full, ExitStatus::Exited(0), "vdsotest failed on the syscall path"); assert!(clock_syscalls(&trace) >= 5000, "only {} clock syscalls", clock_syscalls(&trace)); assert_ne!(vdso::flags() & vdso::VVAR_TSC_OK, 0); } /// `--trace` knows the new fallback calls by name. #[test_case] fn trace_names() { assert_eq!(syscall::trace_name(96), "gettimeofday"); assert_eq!(syscall::trace_name(201), "time"); assert_eq!(syscall::trace_name(309), "getcpu"); } #[panic_handler] fn panic(info: &PanicInfo) -> ! { rustbelt::test_panic_handler(info) }