//! Parses ELF auxiliary vectors. #![allow(dead_code)] pub(crate) const AT_NULL: usize = 0; /// Key to access the CPU Hardware capabilities bitfield. pub(crate) const AT_HWCAP: usize = 16; /// Key to access the CPU Hardware capabilities 2 bitfield. #[cfg(any( target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc", target_arch = "powerpc64" ))] pub(crate) const AT_HWCAP2: usize = 26; /// Cache HWCAP bitfields of the ELF Auxiliary Vector. /// /// If an entry cannot be read all the bits in the bitfield are set to zero. /// This should be interpreted as all the features being disabled. #[derive(Debug, Copy, Clone)] pub(crate) struct AuxVec { pub hwcap: usize, #[cfg(any( target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc", target_arch = "powerpc64" ))] pub hwcap2: usize, } /// ELF Auxiliary Vector /// /// The auxiliary vector is a memory region in a running ELF program's stack /// composed of (key: usize, value: usize) pairs. /// /// The keys used in the aux vector are platform dependent. For Linux, they are /// defined in [linux/auxvec.h][auxvec_h]. The hardware capabilities of a given /// CPU can be queried with the `AT_HWCAP` and `AT_HWCAP2` keys. /// /// There is no perfect way of reading the auxiliary vector. /// /// - If the `std_detect_dlsym_getauxval` cargo feature is enabled, this will use /// `getauxval` if its linked to the binary, and otherwise proceed to a fallback implementation. /// When `std_detect_dlsym_getauxval` is disabled, this will assume that `getauxval` is /// linked to the binary - if that is not the case the behavior is undefined. /// - Otherwise, if the `std_detect_file_io` cargo feature is enabled, it will /// try to read `/proc/self/auxv`. /// - If that fails, this function returns an error. /// /// Note that run-time feature detection is not invoked for features that can /// be detected at compile-time. Also note that if this function returns an /// error, cpuinfo still can (and will) be used to try to perform run-time /// feature detecton on some platforms. /// /// For more information about when `getauxval` is available check the great /// [`auxv` crate documentation][auxv_docs]. /// /// [auxvec_h]: https://github.com/torvalds/linux/blob/master/include/uapi/linux/auxvec.h /// [auxv_docs]: https://docs.rs/auxv/0.3.3/auxv/ pub(crate) fn auxv() -> Result { #[cfg(feature = "std_detect_dlsym_getauxval")] { // Try to call a dynamically-linked getauxval function. if let Ok(hwcap) = getauxval(AT_HWCAP) { // Targets with only AT_HWCAP: #[cfg(any( target_arch = "riscv32", target_arch = "riscv64", target_arch = "mips", target_arch = "mips64" ))] { // Zero could indicate that no features were detected, but it's also used to // indicate an error. In either case, try the fallback. if hwcap != 0 { return Ok(AuxVec { hwcap }); } } // Targets with AT_HWCAP and AT_HWCAP2: #[cfg(any( target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc", target_arch = "powerpc64" ))] { if let Ok(hwcap2) = getauxval(AT_HWCAP2) { // Zero could indicate that no features were detected, but it's also used to // indicate an error. In particular, on many platforms AT_HWCAP2 will be // legitimately zero, since it contains the most recent feature flags. Use the // fallback only if no features were detected at all. if hwcap != 0 || hwcap2 != 0 { return Ok(AuxVec { hwcap, hwcap2 }); } } } drop(hwcap); } } #[cfg(not(feature = "std_detect_dlsym_getauxval"))] { // Targets with only AT_HWCAP: #[cfg(any( target_arch = "riscv32", target_arch = "riscv64", target_arch = "mips", target_arch = "mips64" ))] { let hwcap = unsafe { libc::getauxval(AT_HWCAP as libc::c_ulong) as usize }; // Zero could indicate that no features were detected, but it's also used to indicate // an error. In either case, try the fallback. if hwcap != 0 { return Ok(AuxVec { hwcap }); } } // Targets with AT_HWCAP and AT_HWCAP2: #[cfg(any( target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc", target_arch = "powerpc64" ))] { let hwcap = unsafe { libc::getauxval(AT_HWCAP as libc::c_ulong) as usize }; let hwcap2 = unsafe { libc::getauxval(AT_HWCAP2 as libc::c_ulong) as usize }; // Zero could indicate that no features were detected, but it's also used to indicate // an error. In particular, on many platforms AT_HWCAP2 will be legitimately zero, // since it contains the most recent feature flags. Use the fallback only if no // features were detected at all. if hwcap != 0 || hwcap2 != 0 { return Ok(AuxVec { hwcap, hwcap2 }); } } } #[cfg(feature = "std_detect_file_io")] { // If calling getauxval fails, try to read the auxiliary vector from // its file: auxv_from_file("/proc/self/auxv") } #[cfg(not(feature = "std_detect_file_io"))] { Err(()) } } /// Tries to read the `key` from the auxiliary vector by calling the /// dynamically-linked `getauxval` function. If the function is not linked, /// this function return `Err`. #[cfg(feature = "std_detect_dlsym_getauxval")] fn getauxval(key: usize) -> Result { use libc; pub type F = unsafe extern "C" fn(usize) -> usize; unsafe { let ptr = libc::dlsym(libc::RTLD_DEFAULT, "getauxval\0".as_ptr() as *const _); if ptr.is_null() { return Err(()); } let ffi_getauxval: F = core::mem::transmute(ptr); Ok(ffi_getauxval(key)) } } /// Tries to read the auxiliary vector from the `file`. If this fails, this /// function returns `Err`. #[cfg(feature = "std_detect_file_io")] pub(super) fn auxv_from_file(file: &str) -> Result { let file = super::read_file(file)?; // See . // // The auxiliary vector contains at most 32 (key,value) fields: from // `AT_EXECFN = 31` to `AT_NULL = 0`. That is, a buffer of // 2*32 `usize` elements is enough to read the whole vector. let mut buf = [0_usize; 64]; let len = core::mem::size_of_val(&buf).max(file.len()); unsafe { core::ptr::copy_nonoverlapping(file.as_ptr(), buf.as_mut_ptr() as *mut u8, len); } auxv_from_buf(&buf) } /// Tries to interpret the `buffer` as an auxiliary vector. If that fails, this /// function returns `Err`. #[cfg(feature = "std_detect_file_io")] fn auxv_from_buf(buf: &[usize; 64]) -> Result { // Targets with only AT_HWCAP: #[cfg(any( target_arch = "riscv32", target_arch = "riscv64", target_arch = "mips", target_arch = "mips64", ))] { for el in buf.chunks(2) { match el[0] { AT_NULL => break, AT_HWCAP => return Ok(AuxVec { hwcap: el[1] }), _ => (), } } } // Targets with AT_HWCAP and AT_HWCAP2: #[cfg(any( target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc", target_arch = "powerpc64" ))] { let mut hwcap = None; // For some platforms, AT_HWCAP2 was added recently, so let it default to zero. let mut hwcap2 = 0; for el in buf.chunks(2) { match el[0] { AT_NULL => break, AT_HWCAP => hwcap = Some(el[1]), AT_HWCAP2 => hwcap2 = el[1], _ => (), } } if let Some(hwcap) = hwcap { return Ok(AuxVec { hwcap, hwcap2 }); } } drop(buf); Err(()) } #[cfg(test)] mod tests { extern crate auxv as auxv_crate; use super::*; // Reads the Auxiliary Vector key from /proc/self/auxv // using the auxv crate. #[cfg(feature = "std_detect_file_io")] fn auxv_crate_getprocfs(key: usize) -> Option { use self::auxv_crate::procfs::search_procfs_auxv; use self::auxv_crate::AuxvType; let k = key as AuxvType; match search_procfs_auxv(&[k]) { Ok(v) => Some(v[&k] as usize), Err(_) => None, } } // Reads the Auxiliary Vector key from getauxval() // using the auxv crate. #[cfg(not(any(target_arch = "mips", target_arch = "mips64")))] fn auxv_crate_getauxval(key: usize) -> Option { use self::auxv_crate::getauxval::Getauxval; use self::auxv_crate::AuxvType; let q = auxv_crate::getauxval::NativeGetauxval {}; match q.getauxval(key as AuxvType) { Ok(v) => Some(v as usize), Err(_) => None, } } // FIXME: on mips/mips64 getauxval returns 0, and /proc/self/auxv // does not always contain the AT_HWCAP key under qemu. #[cfg(any( target_arch = "arm", target_arch = "powerpc", target_arch = "powerpc64" ))] #[test] fn auxv_crate() { let v = auxv(); if let Some(hwcap) = auxv_crate_getauxval(AT_HWCAP) { let rt_hwcap = v.expect("failed to find hwcap key").hwcap; assert_eq!(rt_hwcap, hwcap); } // Targets with AT_HWCAP and AT_HWCAP2: #[cfg(any( target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc", target_arch = "powerpc64" ))] { if let Some(hwcap2) = auxv_crate_getauxval(AT_HWCAP2) { let rt_hwcap2 = v.expect("failed to find hwcap2 key").hwcap2; assert_eq!(rt_hwcap2, hwcap2); } } } #[test] fn auxv_dump() { if let Ok(auxvec) = auxv() { println!("{:?}", auxvec); } else { println!("both getauxval() and reading /proc/self/auxv failed!"); } } #[cfg(feature = "std_detect_file_io")] cfg_if::cfg_if! { if #[cfg(target_arch = "arm")] { #[test] fn linux_rpi3() { let file = concat!(env!("CARGO_MANIFEST_DIR"), "/src/detect/test_data/linux-rpi3.auxv"); println!("file: {file}"); let v = auxv_from_file(file).unwrap(); assert_eq!(v.hwcap, 4174038); assert_eq!(v.hwcap2, 16); } #[test] fn linux_macos_vb() { let file = concat!(env!("CARGO_MANIFEST_DIR"), "/src/detect/test_data/macos-virtualbox-linux-x86-4850HQ.auxv"); println!("file: {file}"); // The file contains HWCAP but not HWCAP2. In that case, we treat HWCAP2 as zero. let v = auxv_from_file(file).unwrap(); assert_eq!(v.hwcap, 126614527); assert_eq!(v.hwcap2, 0); } } else if #[cfg(target_arch = "aarch64")] { #[test] fn linux_artificial_aarch64() { let file = concat!(env!("CARGO_MANIFEST_DIR"), "/src/detect/test_data/linux-artificial-aarch64.auxv"); println!("file: {file}"); let v = auxv_from_file(file).unwrap(); assert_eq!(v.hwcap, 0x0123456789abcdef); assert_eq!(v.hwcap2, 0x02468ace13579bdf); } #[test] fn linux_no_hwcap2_aarch64() { let file = concat!(env!("CARGO_MANIFEST_DIR"), "/src/detect/test_data/linux-no-hwcap2-aarch64.auxv"); println!("file: {file}"); let v = auxv_from_file(file).unwrap(); // An absent HWCAP2 is treated as zero, and does not prevent acceptance of HWCAP. assert_ne!(v.hwcap, 0); assert_eq!(v.hwcap2, 0); } } } #[test] #[cfg(feature = "std_detect_file_io")] fn auxv_dump_procfs() { if let Ok(auxvec) = auxv_from_file("/proc/self/auxv") { println!("{:?}", auxvec); } else { println!("reading /proc/self/auxv failed!"); } } #[cfg(any( target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc", target_arch = "powerpc64" ))] #[test] #[cfg(feature = "std_detect_file_io")] fn auxv_crate_procfs() { let v = auxv(); if let Some(hwcap) = auxv_crate_getprocfs(AT_HWCAP) { assert_eq!(v.unwrap().hwcap, hwcap); } // Targets with AT_HWCAP and AT_HWCAP2: #[cfg(any( target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc", target_arch = "powerpc64" ))] { if let Some(hwcap2) = auxv_crate_getprocfs(AT_HWCAP2) { assert_eq!(v.unwrap().hwcap2, hwcap2); } } } }