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_if::cfg_if! {
135 if #[cfg(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 } else {
151 let ffi_getauxval: F = libc::getauxval;
152 }
153 }
154 Ok(unsafe { ffi_getauxval(key as libc::c_ulong) as usize })
155}
156
157/// Tries to read the auxiliary vector from the `file`. If this fails, this
158/// function returns `Err`.
159#[cfg(feature = "std_detect_file_io")]
160pub(super) fn auxv_from_file(file: &str) -> Result<AuxVec, alloc::string::String> {
161 let file = super::read_file(file)?;
162 auxv_from_file_bytes(&file)
163}
164
165/// Read auxiliary vector from a slice of bytes.
166#[cfg(feature = "std_detect_file_io")]
167pub(super) fn auxv_from_file_bytes(bytes: &[u8]) -> Result<AuxVec, alloc::string::String> {
168 // See <https://github.com/torvalds/linux/blob/v5.15/include/uapi/linux/auxvec.h>.
169 //
170 // The auxiliary vector contains at most 34 (key,value) fields: from
171 // `AT_MINSIGSTKSZ` to `AT_NULL`, but its number may increase.
172 let len = bytes.len();
173 let mut buf = alloc::vec![0_usize; 1 + len / core::mem::size_of::<usize>()];
174 unsafe {
175 core::ptr::copy_nonoverlapping(bytes.as_ptr(), buf.as_mut_ptr() as *mut u8, len);
176 }
177
178 auxv_from_buf(&buf)
179}
180
181/// Tries to interpret the `buffer` as an auxiliary vector. If that fails, this
182/// function returns `Err`.
183#[cfg(feature = "std_detect_file_io")]
184fn auxv_from_buf(buf: &[usize]) -> Result<AuxVec, alloc::string::String> {
185 // Targets with only AT_HWCAP:
186 #[cfg(any(
187 target_arch = "riscv32",
188 target_arch = "riscv64",
189 target_arch = "mips",
190 target_arch = "mips64",
191 target_arch = "loongarch32",
192 target_arch = "loongarch64",
193 ))]
194 {
195 for el in buf.chunks(2) {
196 match el[0] {
197 AT_NULL => break,
198 AT_HWCAP => return Ok(AuxVec { hwcap: el[1] }),
199 _ => (),
200 }
201 }
202 }
203 // Targets with AT_HWCAP and AT_HWCAP2:
204 #[cfg(any(
205 target_arch = "aarch64",
206 target_arch = "arm",
207 target_arch = "powerpc",
208 target_arch = "powerpc64",
209 target_arch = "s390x",
210 ))]
211 {
212 let mut hwcap = None;
213 // For some platforms, AT_HWCAP2 was added recently, so let it default to zero.
214 let mut hwcap2 = 0;
215 for el in buf.chunks(2) {
216 match el[0] {
217 AT_NULL => break,
218 AT_HWCAP => hwcap = Some(el[1]),
219 AT_HWCAP2 => hwcap2 = el[1],
220 _ => (),
221 }
222 }
223
224 if let Some(hwcap) = hwcap {
225 return Ok(AuxVec { hwcap, hwcap2 });
226 }
227 }
228 // Suppress unused variable
229 let _ = buf;
230 Err(alloc::string::String::from("hwcap not found"))
231}
232
233#[cfg(test)]
234mod tests;