From 193965dfe5d19a2d3afe75cae3709eeca2b40eb3 Mon Sep 17 00:00:00 2001 From: Jason White Date: Thu, 10 Mar 2022 12:05:42 -0800 Subject: [PATCH] Convert llvm_asm to asm Summary: The `llvm_asm` nightly feature was removed in Rust 1.59 and `asm!()` was simultaneously stabilized, so we need to make the switch so things continue compiling. This retains the `llvm_asm` code so that it can continue compiling on old llvm versions. This will be removed when that is updated. Reviewed By: johnhurt Differential Revision: D34561246 fbshipit-source-id: 42cd2c7e83cea9a5c21bd5871463bb1a42be139d --- reverie-ptrace/src/lib.rs | 2 +- reverie-ptrace/src/perf.rs | 22 +++++++++++- reverie-ptrace/src/validation.rs | 27 ++++++++++++--- tests/basics.rs | 27 +++++++++++---- tests/convert.rs | 59 +++++++++++++++++++++++++++++++- tests/timer_semantics.rs | 21 ++++++++++-- 6 files changed, 143 insertions(+), 15 deletions(-) diff --git a/reverie-ptrace/src/lib.rs b/reverie-ptrace/src/lib.rs index 59cb8b6..31bad6d 100644 --- a/reverie-ptrace/src/lib.rs +++ b/reverie-ptrace/src/lib.rs @@ -36,7 +36,7 @@ #![feature(async_closure)] #![feature(internal_output_capture)] #![feature(never_type)] -#![feature(llvm_asm)] +#![cfg_attr(feature = "llvm_asm", feature(llvm_asm))] #![feature(map_first_last)] #![feature(bench_black_box)] diff --git a/reverie-ptrace/src/perf.rs b/reverie-ptrace/src/perf.rs index ce40583..3918991 100644 --- a/reverie-ptrace/src/perf.rs +++ b/reverie-ptrace/src/perf.rs @@ -569,10 +569,30 @@ macro_rules! ret_without_perf { /// Perform exactly `count+1` conditional branch instructions. Useful for /// testing timer-related code. #[cfg(target_arch = "x86_64")] +#[cfg(not(feature = "llvm_asm"))] +#[inline(never)] +pub fn do_branches(mut count: u64) { + // Anything but assembly is unreliable between debug and release + unsafe { + // Loop until carry flag is set, indicating underflow + core::arch::asm!( + "2:", + "sub {0}, 1", + "jnc 2b", + inout(reg) count, + ) + } + + assert_eq!(count, u64::MAX); +} + +/// Perform exactly `count+1` conditional branch instructions. Useful for +/// testing timer-related code. +#[cfg(target_arch = "x86_64")] +#[cfg(feature = "llvm_asm")] #[inline(never)] pub fn do_branches(count: u64) { // Anything but assembly is unreliable between debug and release - // TODO: Switch to `asm!()` when our LLVM version supports it. #[allow(deprecated)] unsafe { // Loop until carry flag is set, indicating underflow diff --git a/reverie-ptrace/src/validation.rs b/reverie-ptrace/src/validation.rs index d78f037..888d81d 100644 --- a/reverie-ptrace/src/validation.rs +++ b/reverie-ptrace/src/validation.rs @@ -13,7 +13,6 @@ use core::mem; use perf_event_open_sys::bindings as perf; use raw_cpuid::{CpuId, FeatureInfo}; use reverie::Errno; -use std::hint::black_box; use thiserror::Error; use tracing::{error, warn}; @@ -68,6 +67,7 @@ pub(crate) enum PmuValidationError { #[error("Intel Kvm-In-Txcp bug found")] IntelKvmInTxcpBugDetected, + #[cfg(feature = "llvm_asm")] #[error("Overcount triggered by PMU interrupts detected due to Xen PMU virtualization bug")] IntelXenPmiBugDetected, } @@ -361,7 +361,7 @@ fn is_amd_zen(cpu_feature: FeatureInfo) -> bool { /// This is a transcription of the function with the same name in Mozilla-RR it will /// check for bugs specific to cpu architectures -fn check_for_arch_bugs(precise_ip: bool) -> Result<(), PmuValidationError> { +fn check_for_arch_bugs(_precise_ip: bool) -> Result<(), PmuValidationError> { let c = CpuId::new(); let vendor = c.get_vendor_info().unwrap(); let feature_info = c @@ -371,7 +371,12 @@ fn check_for_arch_bugs(precise_ip: bool) -> Result<(), PmuValidationError> { match vendor_str { AMD_VENDOR if is_amd_zen(feature_info) => check_for_zen_speclockmap(), - INTEL_VENDOR => check_for_kvm_in_txcp_bug().and(check_for_xen_pmi_bug(precise_ip)), + INTEL_VENDOR => { + check_for_kvm_in_txcp_bug()?; + #[cfg(feature = "llvm_asm")] + check_for_xen_pmi_bug(_precise_ip)?; + Ok(()) + } s => panic!("Unknown CPU vendor: {}", s), } } @@ -395,6 +400,18 @@ fn check_for_zen_speclockmap() -> Result<(), PmuValidationError> { let count = read_counter(&fd)?; // A lock add is known to increase the perf counter we're looking at. + #[cfg(not(feature = "llvm_asm"))] + unsafe { + let _prev: usize; + core::arch::asm!( + "lock", + "xadd [{}], {}", + in(reg) val, + inout(reg) to_add => _prev, + ) + } + + #[cfg(feature = "llvm_asm")] #[allow(deprecated)] unsafe { let _prev: usize; @@ -437,6 +454,8 @@ fn check_for_kvm_in_txcp_bug() -> Result<(), PmuValidationError> { } } +// FIXME: Convert this big block of llvm_asm over to the new asm syntax. +#[cfg(feature = "llvm_asm")] fn check_for_xen_pmi_bug(precise_ip: bool) -> Result<(), PmuValidationError> { #[allow(unused_assignments)] let mut count: i32 = -1; @@ -451,7 +470,7 @@ fn check_for_xen_pmi_bug(precise_ip: bool) -> Result<(), PmuValidationError> { 20764791 } - let mut accumulator = black_box(make_accumulator_seed()); + let mut accumulator = core::hint::black_box(make_accumulator_seed()); let mut expected_accumulator = accumulator; // reproduce the assembly here to calculate what the final accumulator value should be diff --git a/tests/basics.rs b/tests/basics.rs index c3fc41f..3bd7f77 100644 --- a/tests/basics.rs +++ b/tests/basics.rs @@ -6,7 +6,7 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ -#![feature(llvm_asm)] +#![cfg_attr(feature = "llvm_asm", feature(llvm_asm))] //! Basic tests that don't fall into some other category. @@ -221,12 +221,27 @@ fn i_should_segfault() { fn i_should_segfault_2() { use nix::sys::signal::Signal::SIGSEGV; use reverie_ptrace::testing::test_fn; - let (output, _) = test_fn::(|| unsafe { + + #[inline] + #[cfg(not(feature = "llvm_asm"))] + unsafe fn do_segfault() { + let null_ptr: *const usize = core::ptr::null(); + asm!( + "jmp {0}", + in(reg) null_ptr, + ) + } + + #[inline] + #[cfg(feature = "llvm_asm")] + #[allow(deprecated)] + unsafe fn do_segfault() { llvm_asm!(r#"mov $$0, %rax - jmpq *%rax - "#:::"rax") - }) - .unwrap(); + jmpq *%rax + "#:::"rax") + } + + let (output, _) = test_fn::(|| unsafe { do_segfault() }).unwrap(); assert_eq!(output.status, ExitStatus::Signaled(SIGSEGV, true),); } diff --git a/tests/convert.rs b/tests/convert.rs index 0090861..7fa0e46 100644 --- a/tests/convert.rs +++ b/tests/convert.rs @@ -6,7 +6,7 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ -#![feature(llvm_asm)] +#![cfg_attr(feature = "llvm_asm", feature(llvm_asm))] // when we convert syscall, such as open -> openat, the old syscall // args should not be clobbered, even with the conversion. @@ -61,7 +61,64 @@ mod tests { use reverie_ptrace::testing::check_fn; #[cfg(target_arch = "x86_64")] + #[cfg(not(feature = "llvm_asm"))] #[allow(unused_mut)] + unsafe fn open_syscall_sanity_check() -> i32 { + let path = b"/dev/null\0".as_ptr() as usize; + let flags: usize = 0x8000; // O_LARGEFILE + let mode: usize = 0o644; + + let mut ret: usize; + + // // The following asm block does this: + // let ret = open("/dev/null", 0x8000, 0644); + // if ret >= -4095 as u64 { exit_group(1) } + // // Sanity check input registers to ensure they didn't change. + // if %rsi != 0x8000 { exit_group(1) } + // if %rdx != 0644 { exit_roup(1) } + // return fd + core::arch::asm!( + "mov r8, {arg1}", + "syscall", + // if (ret >= -4095 as u64) goto 1 + "cmp 0xfffffffffffff001" + "jae 2f", + // if (rax != r8) goto label1; + "cmp rdi, r8", + "jne 2f", + // if (rsi != 0x8000) goto label1; + "cmp rsi, 0x8000", + "jne 2f", + // if (rdx != 0644) goto label1; + "cmp rdx, 0x1a4", + "jne 2f", + // Otherwise, we're successful. + "jmp 3f", + "2:", + // Set syscall arg1 to label1 + "mov rdi, 0x1", + // Set syscall to exit_group + "mov rax, {sys_exit_group}", + // Do the syscall + "syscall", + "3:", + lateout("rax") ret, + in("rax") n, + arg1 = inlateout("rdi") path, // Reused for the exit_group syscall. + in("rsi") flags, + in("rdx") mode, + out("r8") _, // Clobbered + out("rcx") _, // rcx is used to store old rip + out("r11") _, // r11 is used to store old rflags + ); + + ret + } + + #[cfg(target_arch = "x86_64")] + #[cfg(feature = "llvm_asm")] + #[allow(unused_mut)] + #[allow(deprecated)] unsafe fn open_syscall_sanity_check() -> i32 { let mut ret; let path = b"/dev/null\0"; diff --git a/tests/timer_semantics.rs b/tests/timer_semantics.rs index 9f53e80..3db1a1e 100644 --- a/tests/timer_semantics.rs +++ b/tests/timer_semantics.rs @@ -12,8 +12,7 @@ //! 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. -#![feature(llvm_asm)] - +#![cfg_attr(feature = "llvm_asm", feature(llvm_asm))] use core::arch::x86_64::{__cpuid, __rdtscp, _rdtsc}; use libc; use reverie::{ @@ -189,7 +188,25 @@ async fn raise_sigwinch>(guest: &mut T) -> Tgkill { .with_sig(libc::SIGWINCH) } +// FIXME: Use the syscalls crate for doing this when it switches to using the +// `asm!()` macro instead of asm inside of a C file. #[cfg(all(target_os = "linux", target_arch = "x86_64"))] +#[cfg(not(feature = "llvm_asm"))] +unsafe fn syscall_no_branches(no: libc::c_long, arg1: libc::c_long) { + let mut ret: u64; + core::arch::asm!( + "syscall", + lateout("rax") ret, + in("rax") no, + in("rdi") arg1, + out("rcx") _, // rcx is used to store old rip + out("r11") _, // r11 is used to store old rflags + ); +} + +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +#[cfg(feature = "llvm_asm")] +#[allow(deprecated)] unsafe fn syscall_no_branches(no: libc::c_long, arg1: libc::c_long) { llvm_asm!(" mov $0, %rax