Get tests building under aarch64

Reviewed By: VladimirMakaev

Differential Revision: D40701839

fbshipit-source-id: e60053c7d21696e7f7dd120420b896dee3d65ba7
This commit is contained in:
Jason White 2022-10-27 10:56:09 -07:00 committed by Facebook GitHub Bot
parent 21f7f9a8a6
commit ada1e3c542
14 changed files with 186 additions and 234 deletions

View file

@ -196,13 +196,22 @@ impl Tool for ChunkyPrintLocal {
let _ = guest.send_rpc(Msg::Tick).await;
match call {
// Here we make some attempt to catch redirections:
// FIXME: De-dup the dup
#[cfg(target_arch = "x86_64")]
Syscall::Dup2(d) => {
let newfd = d.newfd();
if newfd == 1 {
self.stdout_disconnected.store(true, Ordering::SeqCst);
match d.newfd() {
1 => self.stdout_disconnected.store(true, Ordering::SeqCst),
2 => self.stderr_disconnected.store(true, Ordering::SeqCst),
_ => {}
}
if newfd == 2 {
self.stderr_disconnected.store(true, Ordering::SeqCst);
guest.tail_inject(call).await
}
Syscall::Dup3(d) => {
match d.newfd() {
1 => self.stdout_disconnected.store(true, Ordering::SeqCst),
2 => self.stderr_disconnected.store(true, Ordering::SeqCst),
_ => {}
}
guest.tail_inject(call).await

View file

@ -56,21 +56,10 @@ impl Tool for PedigreeLocal {
syscall: Syscall,
) -> Result<i64, Error> {
match syscall {
Syscall::Fork(_) | Syscall::Vfork(_) | Syscall::Clone(_) => {
let retval = guest.inject(syscall).await?;
let pedigree = guest.thread_state_mut().0.fork_mut();
trace!(
"got new pedigree: {:?} => {:x?}",
pedigree,
nix::unistd::Pid::try_from(&pedigree)
);
Ok(retval)
}
Syscall::Getpid(_)
| Syscall::Getppid(_)
| Syscall::Gettid(_)
| Syscall::Getpgid(_)
| Syscall::Getpgrp(_) => {
#[cfg(target_arch = "x86_64")]
Syscall::Fork(_) | Syscall::Vfork(_) => self.handle_fork(syscall, guest).await,
Syscall::Clone(_) => self.handle_fork(syscall, guest).await,
Syscall::Getpid(_) | Syscall::Getppid(_) | Syscall::Gettid(_) | Syscall::Getpgid(_) => {
let pid = guest.inject(syscall).await?;
let vpid = nix::unistd::Pid::try_from(&self.0).unwrap();
trace!("getpid returned {:?} vpid: {:?}", pid, vpid);
@ -84,6 +73,23 @@ impl Tool for PedigreeLocal {
}
}
impl PedigreeLocal {
async fn handle_fork(
&self,
syscall: Syscall,
guest: &mut impl Guest<Self>,
) -> Result<i64, Error> {
let retval = guest.inject(syscall).await?;
let pedigree = guest.thread_state_mut().0.fork_mut();
trace!(
"got new pedigree: {:?} => {:x?}",
pedigree,
nix::unistd::Pid::try_from(&pedigree)
);
Ok(retval)
}
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let args = CommonToolArguments::from_args();

View file

@ -61,39 +61,43 @@ impl From<WriteFamily> for Syscall {
/// Represents the stat family of syscalls. All of these have an associated stat
/// buffer.
// Stat not available in aarch64
#[cfg(not(target_arch = "aarch64"))]
#[derive(From, Debug, Copy, Clone, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum StatFamily {
#[cfg(not(target_arch = "aarch64"))]
Stat(super::Stat),
Fstat(super::Fstat),
#[cfg(not(target_arch = "aarch64"))]
Lstat(super::Lstat),
#[cfg(not(target_arch = "aarch64"))]
Newfstatat(super::Newfstatat),
}
// Stat not available in aarch64
#[cfg(not(target_arch = "aarch64"))]
impl StatFamily {
/// Get address of the stat buffer. Returns `None` if a NULL pointer was
/// specified.
pub fn stat(&self) -> Option<StatPtr> {
match self {
#[cfg(not(target_arch = "aarch64"))]
Self::Stat(s) => s.stat(),
Self::Fstat(s) => s.stat(),
#[cfg(not(target_arch = "aarch64"))]
Self::Lstat(s) => s.stat(),
#[cfg(not(target_arch = "aarch64"))]
Self::Newfstatat(s) => s.stat(),
}
}
}
// Stat not available in aarch64
#[cfg(not(target_arch = "aarch64"))]
impl From<StatFamily> for Syscall {
fn from(family: StatFamily) -> Syscall {
match family {
#[cfg(not(target_arch = "aarch64"))]
StatFamily::Stat(syscall) => Syscall::Stat(syscall),
StatFamily::Fstat(syscall) => Syscall::Fstat(syscall),
#[cfg(not(target_arch = "aarch64"))]
StatFamily::Lstat(syscall) => Syscall::Lstat(syscall),
#[cfg(not(target_arch = "aarch64"))]
StatFamily::Newfstatat(syscall) => Syscall::Newfstatat(syscall),
}
}

View file

@ -79,7 +79,6 @@ syscall_list! {
close => Close,
#[cfg(not(target_arch = "aarch64"))]
stat => Stat,
#[cfg(not(target_arch = "aarch64"))]
fstat => Fstat,
#[cfg(not(target_arch = "aarch64"))]
lstat => Lstat,
@ -392,6 +391,7 @@ syscall_list! {
fchownat => Fchownat,
#[cfg(not(target_arch = "aarch64"))]
futimesat => Futimesat,
// TODO: Rename this to Fstatat.
#[cfg(not(target_arch = "aarch64"))]
newfstatat => Newfstatat,
unlinkat => Unlinkat,
@ -573,8 +573,6 @@ typed_syscall! {
}
}
// Fstat not available in aarch64
#[cfg(not(target_arch = "aarch64"))]
typed_syscall! {
pub struct Fstat {
fd: i32,
@ -2671,7 +2669,8 @@ typed_syscall! {
flags: AtFlags,
}
}
// Stat not available in aarch64
// Newfstatat not available in aarch64
#[cfg(not(target_arch = "aarch64"))]
impl From<Stat> for Newfstatat {
fn from(stat: Stat) -> Self {
@ -2704,6 +2703,7 @@ typed_syscall! {
flags: AtFlags,
}
}
// Unlink not available in aarch64
#[cfg(not(target_arch = "aarch64"))]
impl From<Unlink> for Unlinkat {

View file

@ -226,8 +226,8 @@ fn i_should_segfault() {
use reverie_ptrace::testing::test_fn;
let (output, _) = test_fn::<NoopTool, _>(|| {
unsafe {
let invalid_pointer = 0x5u64 as *mut u64;
std::ptr::write(invalid_pointer, 0xdeadbeefu64);
let invalid_ptr = 0x5u64 as *mut u64;
invalid_ptr.write(0xdeadbeefu64);
};
})
.unwrap();
@ -240,26 +240,15 @@ fn i_should_segfault_2() {
use nix::sys::signal::Signal::SIGSEGV;
use reverie_ptrace::testing::test_fn;
#[inline]
#[cfg(not(feature = "llvm_asm"))]
unsafe fn do_segfault() {
let null_ptr: *const usize = core::ptr::null();
core::arch::asm!(
"jmp {0}",
in(reg) null_ptr,
)
pub fn do_segfault() {
let invalid_ptr = 0x1234 as *const usize;
let result = unsafe { invalid_ptr.read() };
// Print so the above doesn't get optimized out. We will never get here
// because the above segfaults.
println!("{}", result);
}
#[inline]
#[cfg(feature = "llvm_asm")]
#[allow(deprecated)]
unsafe fn do_segfault() {
llvm_asm!(r#"mov $$0, %rax
jmpq *%rax
"#:::"rax")
}
let (output, _) = test_fn::<NoopTool, _>(|| unsafe { do_segfault() }).unwrap();
let (output, _) = test_fn::<NoopTool, _>(|| do_segfault()).unwrap();
assert_eq!(output.status, ExitStatus::Signaled(SIGSEGV, true),);
}

View file

@ -17,7 +17,6 @@ use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use raw_cpuid::cpuid;
use reverie::syscalls::Syscall;
use reverie::CpuIdResult;
use reverie::Errno;
@ -27,8 +26,6 @@ use reverie::GlobalRPC;
use reverie::GlobalTool;
use reverie::Guest;
use reverie::Pid;
use reverie::Rdtsc;
use reverie::RdtscResult;
use reverie::Signal;
use reverie::Tid;
use reverie::TimerSchedule;
@ -124,6 +121,7 @@ impl Tool for LocalState {
guest.tail_inject(syscall).await
}
#[cfg(target_arch = "x86_64")]
async fn handle_cpuid_event<T: Guest<Self>>(
&self,
guest: &mut T,
@ -131,16 +129,17 @@ impl Tool for LocalState {
ecx: u32,
) -> Result<CpuIdResult, Errno> {
guest.send_rpc(IncrMsg::Increment).await;
Ok(cpuid!(eax, ecx))
Ok(raw_cpuid::cpuid!(eax, ecx))
}
#[cfg(target_arch = "x86_64")]
async fn handle_rdtsc_event<T: Guest<Self>>(
&self,
guest: &mut T,
request: Rdtsc,
) -> Result<RdtscResult, Errno> {
request: reverie::Rdtsc,
) -> Result<reverie::RdtscResult, Errno> {
guest.send_rpc(IncrMsg::Increment).await;
Ok(RdtscResult::new(request))
Ok(reverie::RdtscResult::new(request))
}
async fn handle_signal_event<T: Guest<Self>>(
@ -179,13 +178,6 @@ impl Tool for LocalState {
}
}
/// Inform the Tool to begin counting events via a specific syscall
fn do_marker_syscall() {
unsafe {
libc::clock_getres(libc::CLOCK_MONOTONIC, std::ptr::null_mut());
}
}
#[cfg(all(not(sanitized), test))]
mod tests {
use std::time::Duration;
@ -196,6 +188,13 @@ mod tests {
use super::*;
/// Inform the Tool to begin counting events via a specific syscall
fn do_marker_syscall() {
unsafe {
libc::clock_getres(libc::CLOCK_MONOTONIC, std::ptr::null_mut());
}
}
#[test]
fn guest_busywait_no_timer() {
let start = Instant::now();

View file

@ -21,6 +21,7 @@
* 748: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
* 74f: 00
*/
#if defined(__x86_64__)
__attribute__((noinline)) static int sys_getpid(void) {
int ret;
asm volatile(
@ -29,6 +30,14 @@ __attribute__((noinline)) static int sys_getpid(void) {
: "=r"(ret));
return ret;
}
#elif defined(__aarch64__)
__attribute__((noinline)) static int sys_getpid(void) {
register long x8 __asm__("x8") = 172;
register long x0 __asm__("x0");
asm volatile("svc 0" : "=r"(x0) : "r"(x8) : "memory", "cc");
return (int)x0;
}
#endif
int main(int argc, char* argv[]) {
int pid0 = getpid();

View file

@ -7,6 +7,8 @@
*/
#![cfg_attr(feature = "llvm_asm", feature(llvm_asm))]
// FIXME: This test does some very x86_64-specific things.
#![cfg(target_arch = "x86_64")]
// when we convert syscall, such as open -> openat, the old syscall
// args should not be clobbered, even with the conversion.

View file

@ -8,6 +8,9 @@
//! Tests cpuid interception
// cpuid interception is only available on x86_64
#![cfg(target_arch = "x86_64")]
use raw_cpuid::CpuIdResult;
use reverie::Errno;
use reverie::GlobalTool;

View file

@ -6,6 +6,9 @@
* LICENSE file in the root directory of this source tree.
*/
// rdtsc interception is only available on x86_64
#![cfg(target_arch = "x86_64")]
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;

View file

@ -35,6 +35,7 @@ impl Tool for LocalState {
let exit_failure = ExitGroup::new().with_status(1);
match syscall {
// glibc should wrap signalfd -> signalfd4(2).
#[cfg(target_arch = "x86_64")]
Syscall::Signalfd(_) => guest.tail_inject(exit_failure).await,
Syscall::Signalfd4(_) => {
let (_, args) = syscall.into_parts();

View file

@ -8,11 +8,6 @@
// reinject stat* as fstatat unittest
use reverie::syscalls;
use reverie::syscalls::Displayable;
use reverie::syscalls::Syscall;
use reverie::Error;
use reverie::Guest;
use reverie::Tool;
use serde::Deserialize;
use serde::Serialize;
@ -20,31 +15,8 @@ use serde::Serialize;
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
struct LocalState;
async fn handle_newfstatat<T: Guest<LocalState>>(
guest: &mut T,
call: syscalls::Newfstatat,
) -> Result<i64, Error> {
let res = guest.inject(call).await;
println!("{} = {:?}", call.display(&guest.memory()), res);
Ok(res?)
}
#[reverie::tool]
impl Tool for LocalState {
async fn handle_syscall_event<T: Guest<Self>>(
&self,
guest: &mut T,
syscall: Syscall,
) -> Result<i64, Error> {
match syscall {
Syscall::Stat(stat) => handle_newfstatat(guest, stat.into()).await,
Syscall::Lstat(lstat) => handle_newfstatat(guest, lstat.into()).await,
_ => guest.tail_inject(syscall).await,
}
}
}
impl Tool for LocalState {}
#[cfg(all(not(sanitized), test))]
mod tests {
@ -79,7 +51,7 @@ mod tests {
// glibc doesn't provide wrapper for statx
unsafe fn statx(
dirfd: i32,
path: *const i8,
path: *const libc::c_char,
flags: i32,
mask: u32,
statxbuf: *mut libc::statx,

View file

@ -11,10 +11,6 @@
//! 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.
#![cfg_attr(feature = "llvm_asm", feature(llvm_asm))]
use core::arch::x86_64::__cpuid;
use core::arch::x86_64::__rdtscp;
use core::arch::x86_64::_rdtsc;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
@ -202,110 +198,99 @@ async fn raise_sigwinch<T: Guest<LocalState>>(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(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.
#[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
mov $1, %rdi
xor %rsi, %rsi
xor %rdx, %rdx
xor %r10, %r10
xor %r8, %r8
xor %r9, %r9
syscall
"
: /* no output */
: "r"(no), "r"(arg1)
: "cc", "rax", "rdi", "rsi", "rdx", "r10", "r8", "r9", /* from syscall: */ "rcx", "r11"
);
}
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;
fn sched_precise() {
unsafe { syscall_no_branches(libc::SYS_clock_getres, 0) }
}
use super::*;
fn sched_precise_alternate_rcb_count() {
unsafe { syscall_no_branches(libc::SYS_msgrcv, 0) }
}
fn sched_imprecise() {
unsafe { syscall_no_branches(libc::SYS_timer_getoverrun, 0) }
}
fn mark_clock() {
unsafe { syscall_no_branches(libc::SYS_clock_settime, 0) }
}
fn assert_clock(delta: u64) {
unsafe { syscall_no_branches(libc::SYS_clock_adjtime, delta as i64) }
}
fn assert_clock_at_next_timer(value: u64) {
unsafe { syscall_no_branches(libc::SYS_timer_gettime, value as i64) }
}
fn do_syscall() {
unsafe { syscall_no_branches(libc::SYS_clock_gettime, 0) }
}
fn immediate_exit() {
unsafe { syscall_no_branches(libc::SYS_exit, 0) }
}
fn sched_precise_and_raise() {
unsafe { syscall_no_branches(libc::SYS_fanotify_init, 0) }
}
fn sched_imprecise_and_raise() {
unsafe { syscall_no_branches(libc::SYS_fanotify_mark, 0) }
}
fn sched_precise_and_inject() {
unsafe { syscall_no_branches(libc::SYS_msgctl, 0) }
}
fn sched_imprecise_and_inject() {
unsafe { syscall_no_branches(libc::SYS_msgget, 0) }
}
fn cpuid() {
unsafe {
__cpuid(0);
#[inline(always)]
unsafe fn syscall_no_branches(no: Sysno, arg1: libc::c_long) {
syscalls::syscall!(no, arg1, 0, 0, 0, 0, 0);
}
}
fn rdtsc() {
unsafe {
_rdtsc();
fn sched_precise() {
unsafe { syscall_no_branches(Sysno::clock_getres, 0) }
}
}
fn rdtscp() {
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;
__rdtscp(&mut x as *mut _);
core::arch::x86_64::__rdtscp(&mut x as *mut _);
}
}
}
fn ts_check_fn(rcbs: u64, f: impl FnOnce()) -> GlobalState {
fn ts_check_fn(rcbs: u64, f: impl FnOnce()) -> GlobalState {
use reverie_ptrace::testing::check_fn_with_config;
check_fn_with_config::<LocalState, _>(
f,
@ -315,26 +300,10 @@ fn ts_check_fn(rcbs: u64, f: impl FnOnce()) -> GlobalState {
},
true,
)
}
}
const MANY_RCBS: u64 = 10000; // Normal perf signaling
const LESS_RCBS: u64 = 15; // Low enough to use artificial signaling
#[cfg(all(not(sanitized), test))]
mod timer_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::ret_without_perf;
use reverie_ptrace::testing::check_fn_with_config;
use reverie_ptrace::testing::do_branches;
use test_case::test_case;
use super::*;
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)]
@ -641,16 +610,6 @@ mod timer_tests {
}
}
}
}
#[cfg(all(not(sanitized), test))]
mod clock_tests {
use reverie_ptrace::ret_without_perf;
use reverie_ptrace::testing::check_fn;
use reverie_ptrace::testing::do_branches;
use test_case::test_case;
use super::*;
#[test]
fn clock_accuracy() {
@ -722,14 +681,6 @@ mod clock_tests {
});
assert_eq!(gs.num_timer_evts.into_inner(), 1);
}
}
#[cfg(all(not(sanitized), test))]
mod general {
use reverie_ptrace::ret_without_perf;
use reverie_ptrace::testing::check_fn_with_config;
use super::*;
#[test]
fn basic() {

View file

@ -6,6 +6,10 @@
* LICENSE file in the root directory of this source tree.
*/
// FIXME: aarch64 doesn't have a `vfork` syscall. Instead, it uses the `clone`
// syscall. This test should work with both methods of doing a `vfork`.
#![cfg(target_arch = "x86_64")]
// signal handling related tests.
use reverie::syscalls::Syscall;