//! 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", target_arch = "s390x", ))] 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)] #[cfg_attr(test, derive(PartialEq))] pub(crate) struct AuxVec { pub hwcap: usize, #[cfg(any( target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc", target_arch = "powerpc64", target_arch = "s390x", ))] 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. /// /// Note: The `std_detect_dlsym_getauxval` cargo feature is ignored on /// `*-linux-{gnu,musl,ohos}*` and `*-android*` targets because we can safely assume `getauxval` /// is linked to the binary. /// - `*-linux-gnu*` targets ([since Rust 1.64](https://blog.rust-lang.org/2022/08/01/Increasing-glibc-kernel-requirements.html)) /// have glibc requirements higher than [glibc 2.16 that added `getauxval`](https://sourceware.org/legacy-ml/libc-announce/2012/msg00000.html). /// - `*-linux-musl*` targets ([at least since Rust 1.15](https://github.com/rust-lang/rust/blob/1.15.0/src/ci/docker/x86_64-musl/build-musl.sh#L15)) /// use musl newer than [musl 1.1.0 that added `getauxval`](https://git.musl-libc.org/cgit/musl/tree/WHATSNEW?h=v1.1.0#n1197) /// - `*-linux-ohos*` targets use a [fork of musl 1.2](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/native-lib/musl.md) /// - `*-android*` targets ([since Rust 1.68](https://blog.rust-lang.org/2023/01/09/android-ndk-update-r25.html)) /// have the minimum supported API level higher than [Android 4.3 (API level 18) that added `getauxval`](https://github.com/aosp-mirror/platform_bionic/blob/d3ebc2f7c49a9893b114124d4a6b315f3a328764/libc/include/sys/auxv.h#L49). /// /// 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 { // Try to call a 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", target_arch = "loongarch32", target_arch = "loongarch64", ))] { // 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", target_arch = "s390x", ))] { 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 }); } } } // Intentionnaly not used let _ = hwcap; } #[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").map_err(|_| ()) } #[cfg(not(feature = "std_detect_file_io"))] { Err(()) } } /// Tries to read the `key` from the auxiliary vector by calling the /// `getauxval` function. If the function is not linked, this function return `Err`. fn getauxval(key: usize) -> Result { type F = unsafe extern "C" fn(libc::c_ulong) -> libc::c_ulong; cfg_select! { all( feature = "std_detect_dlsym_getauxval", not(all( target_os = "linux", any(target_env = "gnu", target_env = "musl", target_env = "ohos"), )), not(target_os = "android"), ) => { let ffi_getauxval: F = unsafe { let ptr = libc::dlsym(libc::RTLD_DEFAULT, c"getauxval".as_ptr()); if ptr.is_null() { return Err(()); } core::mem::transmute(ptr) }; } _ => { let ffi_getauxval: F = libc::getauxval; } } Ok(unsafe { ffi_getauxval(key as libc::c_ulong) as usize }) } /// 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)?; auxv_from_file_bytes(&file) } /// Read auxiliary vector from a slice of bytes. #[cfg(feature = "std_detect_file_io")] pub(super) fn auxv_from_file_bytes(bytes: &[u8]) -> Result { // See . // // The auxiliary vector contains at most 34 (key,value) fields: from // `AT_MINSIGSTKSZ` to `AT_NULL`, but its number may increase. let len = bytes.len(); let mut buf = alloc::vec![0_usize; 1 + len / core::mem::size_of::()]; unsafe { core::ptr::copy_nonoverlapping(bytes.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]) -> Result { // Targets with only AT_HWCAP: #[cfg(any( target_arch = "riscv32", target_arch = "riscv64", target_arch = "mips", target_arch = "mips64", target_arch = "loongarch32", target_arch = "loongarch64", ))] { 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", target_arch = "s390x", ))] { 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 }); } } // Suppress unused variable let _ = buf; Err(alloc::string::String::from("hwcap not found")) } #[cfg(test)] mod tests;