std_detect/detect/os/linux/
auxvec.rs

1//! Parses ELF auxiliary vectors.
2#![allow(dead_code)]
3
4pub(crate) const AT_NULL: usize = 0;
5
6/// Key to access the CPU Hardware capabilities bitfield.
7pub(crate) const AT_HWCAP: usize = 16;
8/// Key to access the CPU Hardware capabilities 2 bitfield.
9#[cfg(any(
10    target_arch = "aarch64",
11    target_arch = "arm",
12    target_arch = "powerpc",
13    target_arch = "powerpc64",
14    target_arch = "s390x",
15))]
16pub(crate) const AT_HWCAP2: usize = 26;
17
18/// Cache HWCAP bitfields of the ELF Auxiliary Vector.
19///
20/// If an entry cannot be read all the bits in the bitfield are set to zero.
21/// This should be interpreted as all the features being disabled.
22#[derive(Debug, Copy, Clone)]
23#[cfg_attr(test, derive(PartialEq))]
24pub(crate) struct AuxVec {
25    pub hwcap: usize,
26    #[cfg(any(
27        target_arch = "aarch64",
28        target_arch = "arm",
29        target_arch = "powerpc",
30        target_arch = "powerpc64",
31        target_arch = "s390x",
32    ))]
33    pub hwcap2: usize,
34}
35
36/// ELF Auxiliary Vector
37///
38/// The auxiliary vector is a memory region in a running ELF program's stack
39/// composed of (key: usize, value: usize) pairs.
40///
41/// The keys used in the aux vector are platform dependent. For Linux, they are
42/// defined in [linux/auxvec.h][auxvec_h]. The hardware capabilities of a given
43/// CPU can be queried with the  `AT_HWCAP` and `AT_HWCAP2` keys.
44///
45/// There is no perfect way of reading the auxiliary vector.
46///
47/// - If the `std_detect_dlsym_getauxval` cargo feature is enabled, this will use
48///   `getauxval` if its linked to the binary, and otherwise proceed to a fallback implementation.
49///   When `std_detect_dlsym_getauxval` is disabled, this will assume that `getauxval` is
50///   linked to the binary - if that is not the case the behavior is undefined.
51/// - Otherwise, if the `std_detect_file_io` cargo feature is enabled, it will
52///   try to read `/proc/self/auxv`.
53/// - If that fails, this function returns an error.
54///
55/// Note that run-time feature detection is not invoked for features that can
56/// be detected at compile-time.
57///
58///  Note: The `std_detect_dlsym_getauxval` cargo feature is ignored on
59/// `*-linux-{gnu,musl,ohos}*` and `*-android*` targets because we can safely assume `getauxval`
60/// is linked to the binary.
61/// - `*-linux-gnu*` targets ([since Rust 1.64](https://blog.rust-lang.org/2022/08/01/Increasing-glibc-kernel-requirements.html))
62///   have glibc requirements higher than [glibc 2.16 that added `getauxval`](https://sourceware.org/legacy-ml/libc-announce/2012/msg00000.html).
63/// - `*-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))
64///   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)
65/// - `*-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)
66/// - `*-android*` targets ([since Rust 1.68](https://blog.rust-lang.org/2023/01/09/android-ndk-update-r25.html))
67///   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).
68///
69/// For more information about when `getauxval` is available check the great
70/// [`auxv` crate documentation][auxv_docs].
71///
72/// [auxvec_h]: https://github.com/torvalds/linux/blob/master/include/uapi/linux/auxvec.h
73/// [auxv_docs]: https://docs.rs/auxv/0.3.3/auxv/
74pub(crate) fn auxv() -> Result<AuxVec, ()> {
75    // Try to call a getauxval function.
76    if let Ok(hwcap) = getauxval(AT_HWCAP) {
77        // Targets with only AT_HWCAP:
78        #[cfg(any(
79            target_arch = "riscv32",
80            target_arch = "riscv64",
81            target_arch = "mips",
82            target_arch = "mips64",
83            target_arch = "loongarch32",
84            target_arch = "loongarch64",
85        ))]
86        {
87            // Zero could indicate that no features were detected, but it's also used to indicate
88            // an error. In either case, try the fallback.
89            if hwcap != 0 {
90                return Ok(AuxVec { hwcap });
91            }
92        }
93
94        // Targets with AT_HWCAP and AT_HWCAP2:
95        #[cfg(any(
96            target_arch = "aarch64",
97            target_arch = "arm",
98            target_arch = "powerpc",
99            target_arch = "powerpc64",
100            target_arch = "s390x",
101        ))]
102        {
103            if let Ok(hwcap2) = getauxval(AT_HWCAP2) {
104                // Zero could indicate that no features were detected, but it's also used to indicate
105                // an error. In particular, on many platforms AT_HWCAP2 will be legitimately zero,
106                // since it contains the most recent feature flags. Use the fallback only if no
107                // features were detected at all.
108                if hwcap != 0 || hwcap2 != 0 {
109                    return Ok(AuxVec { hwcap, hwcap2 });
110                }
111            }
112        }
113
114        // Intentionnaly not used
115        let _ = hwcap;
116    }
117
118    #[cfg(feature = "std_detect_file_io")]
119    {
120        // If calling getauxval fails, try to read the auxiliary vector from
121        // its file:
122        auxv_from_file("/proc/self/auxv").map_err(|_| ())
123    }
124    #[cfg(not(feature = "std_detect_file_io"))]
125    {
126        Err(())
127    }
128}
129
130/// Tries to read the `key` from the auxiliary vector by calling the
131/// `getauxval` function. If the function is not linked, this function return `Err`.
132fn getauxval(key: usize) -> Result<usize, ()> {
133    type F = unsafe extern "C" fn(libc::c_ulong) -> libc::c_ulong;
134    cfg_select! {
135        all(
136            feature = "std_detect_dlsym_getauxval",
137            not(all(
138                target_os = "linux",
139                any(target_env = "gnu", target_env = "musl", target_env = "ohos"),
140            )),
141            not(target_os = "android"),
142        ) => {
143            let ffi_getauxval: F = unsafe {
144                let ptr = libc::dlsym(libc::RTLD_DEFAULT, c"getauxval".as_ptr());
145                if ptr.is_null() {
146                    return Err(());
147                }
148                core::mem::transmute(ptr)
149            };
150        }
151        _ => {
152            let ffi_getauxval: F = libc::getauxval;
153        }
154    }
155    Ok(unsafe { ffi_getauxval(key as libc::c_ulong) as usize })
156}
157
158/// Tries to read the auxiliary vector from the `file`. If this fails, this
159/// function returns `Err`.
160#[cfg(feature = "std_detect_file_io")]
161pub(super) fn auxv_from_file(file: &str) -> Result<AuxVec, alloc::string::String> {
162    let file = super::read_file(file)?;
163    auxv_from_file_bytes(&file)
164}
165
166/// Read auxiliary vector from a slice of bytes.
167#[cfg(feature = "std_detect_file_io")]
168pub(super) fn auxv_from_file_bytes(bytes: &[u8]) -> Result<AuxVec, alloc::string::String> {
169    // See <https://github.com/torvalds/linux/blob/v5.15/include/uapi/linux/auxvec.h>.
170    //
171    // The auxiliary vector contains at most 34 (key,value) fields: from
172    // `AT_MINSIGSTKSZ` to `AT_NULL`, but its number may increase.
173    let len = bytes.len();
174    let mut buf = alloc::vec![0_usize; 1 + len / core::mem::size_of::<usize>()];
175    unsafe {
176        core::ptr::copy_nonoverlapping(bytes.as_ptr(), buf.as_mut_ptr() as *mut u8, len);
177    }
178
179    auxv_from_buf(&buf)
180}
181
182/// Tries to interpret the `buffer` as an auxiliary vector. If that fails, this
183/// function returns `Err`.
184#[cfg(feature = "std_detect_file_io")]
185fn auxv_from_buf(buf: &[usize]) -> Result<AuxVec, alloc::string::String> {
186    // Targets with only AT_HWCAP:
187    #[cfg(any(
188        target_arch = "riscv32",
189        target_arch = "riscv64",
190        target_arch = "mips",
191        target_arch = "mips64",
192        target_arch = "loongarch32",
193        target_arch = "loongarch64",
194    ))]
195    {
196        for el in buf.chunks(2) {
197            match el[0] {
198                AT_NULL => break,
199                AT_HWCAP => return Ok(AuxVec { hwcap: el[1] }),
200                _ => (),
201            }
202        }
203    }
204    // Targets with AT_HWCAP and AT_HWCAP2:
205    #[cfg(any(
206        target_arch = "aarch64",
207        target_arch = "arm",
208        target_arch = "powerpc",
209        target_arch = "powerpc64",
210        target_arch = "s390x",
211    ))]
212    {
213        let mut hwcap = None;
214        // For some platforms, AT_HWCAP2 was added recently, so let it default to zero.
215        let mut hwcap2 = 0;
216        for el in buf.chunks(2) {
217            match el[0] {
218                AT_NULL => break,
219                AT_HWCAP => hwcap = Some(el[1]),
220                AT_HWCAP2 => hwcap2 = el[1],
221                _ => (),
222            }
223        }
224
225        if let Some(hwcap) = hwcap {
226            return Ok(AuxVec { hwcap, hwcap2 });
227        }
228    }
229    // Suppress unused variable
230    let _ = buf;
231    Err(alloc::string::String::from("hwcap not found"))
232}
233
234#[cfg(test)]
235mod tests;