diff --git a/.github/.cspell/project-dictionary.txt b/.github/.cspell/project-dictionary.txt index 950f5bc1..ec44da19 100644 --- a/.github/.cspell/project-dictionary.txt +++ b/.github/.cspell/project-dictionary.txt @@ -46,7 +46,9 @@ dlfcn dlsym DWCAS ecall +EINVAL elems +ENOENT espup evbmips exynos diff --git a/src/imp/detect/README.md b/src/imp/detect/README.md index ea7750e8..77f0e6cf 100644 --- a/src/imp/detect/README.md +++ b/src/imp/detect/README.md @@ -16,8 +16,7 @@ Here is the table of targets that support run-time CPU feature detection and the | aarch64 | illumos | getisax | lse, lse2 | Disabled by default | | aarch64/arm64ec | windows | IsProcessorFeaturePresent | lse | Enabled by default | | aarch64 | fuchsia | zx_system_get_features | lse | Enabled by default | -| riscv32 | linux | riscv_hwprobe | all | Disabled by default | -| riscv64 | linux | riscv_hwprobe | all | Disabled by default | +| riscv32/riscv64 | linux/android | riscv_hwprobe | all | Disabled by default | | powerpc64 | linux | getauxval | all | Disabled by default | | powerpc64 | freebsd | elf_aux_info | all | Disabled by default | | powerpc64 | openbsd | elf_aux_info | all | Disabled by default | diff --git a/src/imp/detect/aarch64_aa64reg.rs b/src/imp/detect/aarch64_aa64reg.rs index e041b332..392f0e9a 100644 --- a/src/imp/detect/aarch64_aa64reg.rs +++ b/src/imp/detect/aarch64_aa64reg.rs @@ -358,7 +358,7 @@ mod tests { #[allow(clippy::cast_possible_wrap)] #[cfg(target_os = "netbsd")] #[test] - fn test_netbsd() { + fn test_alternative() { use c_types::*; use imp::ffi; #[cfg(not(portable_atomic_no_asm))] @@ -371,7 +371,7 @@ mod tests { // much as Linux does (It may actually be stable enough, though: https://lists.llvm.org/pipermail/llvm-dev/2019-June/133393.html). // // This is currently used only for testing. - unsafe fn sysctl_cpu_id_asm_syscall(name: &[&[u8]]) -> Result { + unsafe fn sysctl_cpu_id_no_libc(name: &[&[u8]]) -> Result { // https://github.com/golang/go/blob/4badad8d477ffd7a6b762c35bc69aed82faface7/src/syscall/asm_netbsd_arm64.s #[inline] unsafe fn sysctl( @@ -489,7 +489,7 @@ mod tests { unsafe { assert_eq!( imp::sysctl_cpu_id(b"machdep.cpu0.cpu_id\0").unwrap(), - sysctl_cpu_id_asm_syscall(&[b"machdep", b"cpu0", b"cpu_id"]).unwrap() + sysctl_cpu_id_no_libc(&[b"machdep", b"cpu0", b"cpu_id"]).unwrap() ); } } diff --git a/src/imp/detect/aarch64_apple.rs b/src/imp/detect/aarch64_apple.rs index 1dd3ddb7..b80ebf00 100644 --- a/src/imp/detect/aarch64_apple.rs +++ b/src/imp/detect/aarch64_apple.rs @@ -29,7 +29,7 @@ mod ffi { extern "C" { // https://developer.apple.com/documentation/kernel/1387446-sysctlbyname - // https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/sys/sysctl.h + // https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/sys/sysctl.h pub(crate) fn sysctlbyname( name: *const c_char, old_p: *mut c_void, @@ -122,6 +122,142 @@ mod tests { } } + #[cfg(target_pointer_width = "64")] + #[test] + fn test_alternative() { + use c_types::*; + #[cfg(not(portable_atomic_no_asm))] + use std::arch::asm; + use std::mem; + use test_helper::{libc, sys}; + // Call syscall using asm instead of libc. + // Note that macOS does not guarantee the stability of raw syscall. + // (And they actually changed it: https://go-review.googlesource.com/c/go/+/25495) + // + // This is currently used only for testing. + unsafe fn sysctlbyname32_no_libc(name: &[u8]) -> Result { + // https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/kern/syscalls.master#L298 + #[inline] + unsafe fn sysctl( + name: *const c_int, + name_len: c_uint, + old_p: *mut c_void, + old_len_p: *mut c_size_t, + new_p: *const c_void, + new_len: c_size_t, + ) -> Result { + // https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/osfmk/mach/i386/syscall_sw.h#L158 + #[inline(always)] + fn syscall_construct_unix(n: u64) -> u64 { + const SYSCALL_CLASS_UNIX: u64 = 2; + const SYSCALL_CLASS_SHIFT: u64 = 24; + const SYSCALL_CLASS_MASK: u64 = 0xFF << SYSCALL_CLASS_SHIFT; + const SYSCALL_NUMBER_MASK: u64 = !SYSCALL_CLASS_MASK; + (SYSCALL_CLASS_UNIX << SYSCALL_CLASS_SHIFT) | (SYSCALL_NUMBER_MASK & n) + } + #[allow(clippy::cast_possible_truncation)] + // SAFETY: the caller must uphold the safety contract. + unsafe { + // https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/kern/syscalls.master#L4 + let mut n = syscall_construct_unix(202); + let r: i64; + asm!( + "svc 0", + "b.cc 2f", + "mov x16, x0", + "mov x0, #-1", + "2:", + inout("x16") n, + inout("x0") ptr_reg!(name) => r, + inout("x1") name_len as u64 => _, + in("x2") ptr_reg!(old_p), + in("x3") ptr_reg!(old_len_p), + in("x4") ptr_reg!(new_p), + in("x5") new_len as u64, + options(nostack), + ); + if r as c_int == -1 { + Err(n as c_int) + } else { + Ok(r as c_int) + } + } + } + // https://github.com/apple-oss-distributions/Libc/blob/af11da5ca9d527ea2f48bb7efbd0f0f2a4ea4812/gen/FreeBSD/sysctlbyname.c + unsafe fn sysctlbyname( + name: &[u8], + old_p: *mut c_void, + old_len_p: *mut c_size_t, + new_p: *mut c_void, + new_len: c_size_t, + ) -> Result { + let mut real_oid: [c_int; sys::CTL_MAXNAME as usize + 2] = unsafe { mem::zeroed() }; + + // Note that this is undocumented API. + // Although FreeBSD defined it in sys/sysctl.h since https://github.com/freebsd/freebsd-src/commit/382e01c8dc7f328f46c61c82a29222f432f510f7 + let mut name2oid_oid: [c_int; 2] = [0, 3]; + + let mut oid_len = mem::size_of_val(&real_oid); + unsafe { + sysctl( + name2oid_oid.as_mut_ptr(), + 2, + real_oid.as_mut_ptr().cast::(), + &mut oid_len, + name.as_ptr().cast::() as *mut c_void, + name.len() - 1, + )? + }; + oid_len /= mem::size_of::(); + unsafe { + sysctl(real_oid.as_mut_ptr(), oid_len as u32, old_p, old_len_p, new_p, new_len) + } + } + + const OUT_LEN: ffi::c_size_t = core::mem::size_of::() as ffi::c_size_t; + + debug_assert_eq!(name.last(), Some(&0), "{:?}", name); + debug_assert_eq!(name.iter().filter(|&&v| v == 0).count(), 1, "{:?}", name); + + let mut out = 0_u32; + let mut out_len = OUT_LEN; + // SAFETY: + // - the caller must guarantee that `name` a valid C string. + // - `out_len` does not exceed the size of `out`. + // - `sysctlbyname` is thread-safe. + let res = unsafe { + sysctlbyname( + name, + (&mut out as *mut u32).cast::(), + &mut out_len, + ptr::null_mut(), + 0, + )? + }; + debug_assert_eq!(res, 0); + debug_assert_eq!(out_len, OUT_LEN); + Ok(out) + } + + for name in [ + &b"hw.optional.armv8_1_atomics\0"[..], + b"hw.optional.arm.FEAT_LSE\0", + b"hw.optional.arm.FEAT_LSE2\0", + b"hw.optional.arm.FEAT_LSE128\0", + b"hw.optional.arm.FEAT_LRCPC\0", + b"hw.optional.arm.FEAT_LRCPC2\0", + b"hw.optional.arm.FEAT_LRCPC3\0", + ] { + unsafe { + if let Some(res) = sysctlbyname32(name) { + assert_eq!(res, sysctlbyname32_no_libc(name).unwrap()); + } else { + assert_eq!(sysctlbyname32_no_libc(name).unwrap_err(), libc::ENOENT); + } + } + } + } + // Static assertions for FFI bindings. // This checks that FFI bindings defined in this crate, FFI bindings defined // in libc, and FFI bindings generated for the platform's latest header file diff --git a/src/imp/detect/auxv.rs b/src/imp/detect/auxv.rs index 32e7f581..a911aff5 100644 --- a/src/imp/detect/auxv.rs +++ b/src/imp/detect/auxv.rs @@ -360,10 +360,30 @@ mod arch { mod tests { use super::*; + #[allow(clippy::cast_sign_loss)] + #[cfg(all(target_arch = "aarch64", target_os = "android"))] + #[test] + fn test_android() { + unsafe { + let mut arch = [1; ffi::PROP_VALUE_MAX as usize]; + let len = ffi::__system_property_get( + b"ro.arch\0".as_ptr().cast::(), + arch.as_mut_ptr().cast::(), + ); + assert!(len >= 0); + std::eprintln!("len={}", len); + std::eprintln!("arch={:?}", arch); + std::eprintln!( + "arch={:?}", + core::str::from_utf8(core::slice::from_raw_parts(arch.as_ptr(), len as usize)) + .unwrap() + ); + } + } + #[cfg(any(target_os = "linux", target_os = "android"))] - #[cfg(target_pointer_width = "64")] #[test] - fn test_linux_like() { + fn test_alternative() { use c_types::*; #[cfg(not(portable_atomic_no_asm))] use std::arch::asm; @@ -372,10 +392,12 @@ mod tests { // Linux kernel 6.4 has added a way to read auxv without depending on either libc or mrs trap. // https://github.com/torvalds/linux/commit/ddc65971bb677aa9f6a4c21f76d3133e106f88eb + // (Actually 6.5? https://github.com/torvalds/linux/commit/636e348353a7cc52609fdba5ff3270065da140d5) // // This is currently used only for testing. - fn getauxval_pr_get_auxv(type_: ffi::c_ulong) -> Result { - #[cfg(target_arch = "aarch64")] + #[cfg(target_pointer_width = "64")] + fn getauxval_pr_get_auxv_no_libc(type_: c_ulong) -> Result { + #[cfg(all(target_arch = "aarch64", target_pointer_width = "64"))] unsafe fn prctl_get_auxv(out: *mut c_void, len: usize) -> Result { let r: i64; unsafe { @@ -448,6 +470,39 @@ mod tests { } Err(0) } + // Similar to the above, but call libc prctl instead of syscall using asm. + // + // This is currently used only for testing. + fn getauxval_pr_get_auxv_libc(type_: c_ulong) -> Result { + unsafe fn prctl_get_auxv(out: *mut c_void, len: usize) -> Result { + // arg4 and arg5 must be zero. + #[allow(clippy::cast_possible_wrap)] + let r = unsafe { sys::prctl(sys::PR_GET_AUXV as c_int, out, len, 0, 0) }; + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + if (r as c_int) < 0 { + Err(r as c_int) + } else { + Ok(r as usize) + } + } + + let mut auxv = vec![unsafe { mem::zeroed::() }; 38]; + + let old_len = auxv.len() * mem::size_of::(); + + // SAFETY: + // - `out_len` does not exceed the size of `auxv`. + let _len = unsafe { prctl_get_auxv(auxv.as_mut_ptr().cast::(), old_len)? }; + + for aux in &auxv { + if aux.a_type == type_ { + // SAFETY: aux.a_un is #[repr(C)] union and all fields have + // the same size and can be safely transmuted to integers. + return Ok(unsafe { aux.a_un.a_val }); + } + } + Err(0) + } unsafe { let mut u = mem::zeroed(); @@ -460,47 +515,47 @@ mod tests { // TODO: qemu-user bug? if (major, minor) < (6, 4) || cfg!(qemu) { std::eprintln!("kernel version: {}.{} (no pr_get_auxv)", major, minor); - assert_eq!(getauxval_pr_get_auxv(ffi::AT_HWCAP).unwrap_err(), -22); - assert_eq!(getauxval_pr_get_auxv(ffi::AT_HWCAP2).unwrap_err(), -22); + assert_eq!(getauxval_pr_get_auxv_libc(ffi::AT_HWCAP).unwrap_err(), -1); + assert_eq!(getauxval_pr_get_auxv_libc(ffi::AT_HWCAP2).unwrap_err(), -1); + #[cfg(target_pointer_width = "64")] + { + assert_eq!( + getauxval_pr_get_auxv_no_libc(ffi::AT_HWCAP).unwrap_err(), + -libc::EINVAL + ); + assert_eq!( + getauxval_pr_get_auxv_no_libc(ffi::AT_HWCAP2).unwrap_err(), + -libc::EINVAL + ); + } } else { std::eprintln!("kernel version: {}.{} (has pr_get_auxv)", major, minor); assert_eq!( os::getauxval(ffi::AT_HWCAP), - getauxval_pr_get_auxv(ffi::AT_HWCAP).unwrap() + getauxval_pr_get_auxv_libc(ffi::AT_HWCAP).unwrap() ); assert_eq!( os::getauxval(ffi::AT_HWCAP2), - getauxval_pr_get_auxv(ffi::AT_HWCAP2).unwrap() + getauxval_pr_get_auxv_libc(ffi::AT_HWCAP2).unwrap() ); + #[cfg(target_pointer_width = "64")] + { + assert_eq!( + os::getauxval(ffi::AT_HWCAP), + getauxval_pr_get_auxv_no_libc(ffi::AT_HWCAP).unwrap() + ); + assert_eq!( + os::getauxval(ffi::AT_HWCAP2), + getauxval_pr_get_auxv_no_libc(ffi::AT_HWCAP2).unwrap() + ); + } } } } - - #[allow(clippy::cast_sign_loss)] - #[cfg(all(target_arch = "aarch64", target_os = "android"))] - #[test] - fn test_android() { - unsafe { - let mut arch = [1; ffi::PROP_VALUE_MAX as usize]; - let len = ffi::__system_property_get( - b"ro.arch\0".as_ptr().cast::(), - arch.as_mut_ptr().cast::(), - ); - assert!(len >= 0); - std::eprintln!("len={}", len); - std::eprintln!("arch={:?}", arch); - std::eprintln!( - "arch={:?}", - core::str::from_utf8(core::slice::from_raw_parts(arch.as_ptr(), len as usize)) - .unwrap() - ); - } - } - #[allow(clippy::cast_possible_wrap)] #[cfg(target_os = "freebsd")] #[test] - fn test_freebsd() { + fn test_alternative() { use c_types::*; #[cfg(not(portable_atomic_no_asm))] use std::arch::asm; @@ -517,15 +572,11 @@ mod tests { // Note that FreeBSD 11 (11.4) was EoL on 2021-09-30, and FreeBSD 11.3 was EoL on 2020-09-30: // https://www.freebsd.org/security/unsupported // - // std_detect uses this way, but it appears to be somewhat incorrect - // (the type of arg4 of sysctl, auxv is smaller than AT_COUNT, etc.). - // https://github.com/rust-lang/stdarch/blob/a0c30f3e3c75adcd6ee7efc94014ebcead61c507/crates/std_detect/src/detect/os/freebsd/auxvec.rs#L52 - // // This is currently used only for testing. // If you want us to use this implementation for compatibility with the older FreeBSD // version that came to EoL a few years ago, please open an issue. - fn getauxval_sysctl_libc(type_: ffi::c_int) -> ffi::c_ulong { - let mut auxv: [sys::Elf64_Auxinfo; sys::AT_COUNT as usize] = unsafe { mem::zeroed() }; + fn getauxval_sysctl_libc(type_: ffi::c_int) -> Result { + let mut auxv: [sys::Elf_Auxinfo; sys::AT_COUNT as usize] = unsafe { mem::zeroed() }; let mut len = core::mem::size_of_val(&auxv) as c_size_t; @@ -553,17 +604,18 @@ mod tests { 0, ) }; + if res == -1 { + return Err(res); + } - if res != -1 { - for aux in &auxv { - if aux.a_type == type_ as c_long { - // SAFETY: aux.a_un is #[repr(C)] union and all fields have - // the same size and can be safely transmuted to integers. - return unsafe { aux.a_un.a_val as c_ulong }; - } + for aux in &auxv { + if aux.a_type == type_ as c_long { + // SAFETY: aux.a_un is #[repr(C)] union and all fields have + // the same size and can be safely transmuted to integers. + return Ok(unsafe { aux.a_un.a_val as c_ulong }); } } - 0 + Err(0) } // Similar to the above, but call syscall using asm instead of libc. // Note that FreeBSD does not guarantee the stability of raw syscall as @@ -572,7 +624,7 @@ mod tests { // https://github.com/ziglang/zig/issues/16590). // // This is currently used only for testing. - fn getauxval_sysctl_asm_syscall(type_: ffi::c_int) -> Result { + fn getauxval_sysctl_no_libc(type_: ffi::c_int) -> Result { #[allow(non_camel_case_types)] type pid_t = c_int; @@ -704,7 +756,7 @@ mod tests { } } - let mut auxv: [sys::Elf64_Auxinfo; sys::AT_COUNT as usize] = unsafe { mem::zeroed() }; + let mut auxv: [sys::Elf_Auxinfo; sys::AT_COUNT as usize] = unsafe { mem::zeroed() }; let mut len = core::mem::size_of_val(&auxv) as c_size_t; @@ -742,16 +794,17 @@ mod tests { Err(0) } - assert_eq!(os::getauxval(ffi::AT_HWCAP), getauxval_sysctl_libc(ffi::AT_HWCAP)); - assert_eq!(os::getauxval(ffi::AT_HWCAP2), getauxval_sysctl_libc(ffi::AT_HWCAP2)); + assert_eq!(os::getauxval(ffi::AT_HWCAP), getauxval_sysctl_libc(ffi::AT_HWCAP).unwrap()); assert_eq!( - os::getauxval(ffi::AT_HWCAP), - getauxval_sysctl_asm_syscall(ffi::AT_HWCAP).unwrap() + os::getauxval(ffi::AT_HWCAP2), + // AT_HWCAP2 is only available on FreeBSD 13+, at least on AArch64. + getauxval_sysctl_libc(ffi::AT_HWCAP2).unwrap_or(0) ); + assert_eq!(os::getauxval(ffi::AT_HWCAP), getauxval_sysctl_no_libc(ffi::AT_HWCAP).unwrap()); assert_eq!( os::getauxval(ffi::AT_HWCAP2), // AT_HWCAP2 is only available on FreeBSD 13+, at least on AArch64. - getauxval_sysctl_asm_syscall(ffi::AT_HWCAP2).unwrap_or(0) + getauxval_sysctl_no_libc(ffi::AT_HWCAP2).unwrap_or(0) ); } diff --git a/src/imp/detect/riscv_linux.rs b/src/imp/detect/riscv_linux.rs index 370bcc0e..e094d4a4 100644 --- a/src/imp/detect/riscv_linux.rs +++ b/src/imp/detect/riscv_linux.rs @@ -125,9 +125,9 @@ mod tests { // We use asm-based syscall for compatibility with non-libc targets. // This test tests that our ones and libc::syscall returns the same result. #[test] - fn test_linux_like() { - use test_helper::libc; - unsafe fn __libc_riscv_hwprobe( + fn test_alternative() { + use test_helper::sys; + unsafe fn __riscv_hwprobe_libc( pairs: *mut ffi::riscv_hwprobe, pair_count: ffi::c_size_t, cpu_set_size: ffi::c_size_t, @@ -136,15 +136,15 @@ mod tests { ) -> ffi::c_long { // SAFETY: the caller must uphold the safety contract. unsafe { - libc::syscall(ffi::__NR_riscv_hwprobe, pairs, pair_count, cpu_set_size, cpus, flags) + sys::syscall(ffi::__NR_riscv_hwprobe, pairs, pair_count, cpu_set_size, cpus, flags) } } - fn libc_riscv_hwprobe(out: &mut ffi::riscv_hwprobe) -> bool { - unsafe { __libc_riscv_hwprobe(out, 1, 0, ptr::null_mut(), 0) == 0 } + fn riscv_hwprobe_libc(out: &mut ffi::riscv_hwprobe) -> bool { + unsafe { __riscv_hwprobe_libc(out, 1, 0, ptr::null_mut(), 0) == 0 } } let mut out = ffi::riscv_hwprobe { key: ffi::RISCV_HWPROBE_KEY_IMA_EXT_0, value: 0 }; let mut libc_out = ffi::riscv_hwprobe { key: ffi::RISCV_HWPROBE_KEY_IMA_EXT_0, value: 0 }; - assert_eq!(riscv_hwprobe(&mut out), libc_riscv_hwprobe(&mut libc_out)); + assert_eq!(riscv_hwprobe(&mut out), riscv_hwprobe_libc(&mut libc_out)); assert_eq!(out, libc_out); }