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 [`getauxval`] is linked to the binary we use it, and otherwise it will
48/// try to read `/proc/self/auxv`.
49/// - If that fails, this function returns an error.
50///
51/// Note that run-time feature detection is not invoked for features that can
52/// be detected at compile-time.
53///
54/// Note: We always directly use `getauxval` on `*-linux-{gnu,musl,ohos}*` and
55/// `*-android*` targets rather than `dlsym` it because we can safely assume
56/// `getauxval` is linked to the binary.
57/// - `*-linux-gnu*` targets ([since Rust 1.64](https://blog.rust-lang.org/2022/08/01/Increasing-glibc-kernel-requirements.html))
58/// have glibc requirements higher than [glibc 2.16 that added `getauxval`](https://sourceware.org/legacy-ml/libc-announce/2012/msg00000.html).
59/// - `*-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))
60/// 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)
61/// - `*-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)
62/// - `*-android*` targets ([since Rust 1.68](https://blog.rust-lang.org/2023/01/09/android-ndk-update-r25.html))
63/// 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).
64///
65/// For more information about when `getauxval` is available check the great
66/// [`auxv` crate documentation][auxv_docs].
67///
68/// [auxvec_h]: https://github.com/torvalds/linux/blob/master/include/uapi/linux/auxvec.h
69/// [auxv_docs]: https://docs.rs/auxv/0.3.3/auxv/
70/// [`getauxval`]: https://man7.org/linux/man-pages/man3/getauxval.3.html
71pub(crate) fn auxv() -> Result<AuxVec, ()> {
72 // Try to call a getauxval function.
73 if let Ok(hwcap) = getauxval(AT_HWCAP) {
74 // Targets with only AT_HWCAP:
75 #[cfg(any(
76 target_arch = "riscv32",
77 target_arch = "riscv64",
78 target_arch = "mips",
79 target_arch = "mips64",
80 target_arch = "loongarch32",
81 target_arch = "loongarch64",
82 ))]
83 {
84 // Zero could indicate that no features were detected, but it's also used to indicate
85 // an error. In either case, try the fallback.
86 if hwcap != 0 {
87 return Ok(AuxVec { hwcap });
88 }
89 }
90
91 // Targets with AT_HWCAP and AT_HWCAP2:
92 #[cfg(any(
93 target_arch = "aarch64",
94 target_arch = "arm",
95 target_arch = "powerpc",
96 target_arch = "powerpc64",
97 target_arch = "s390x",
98 ))]
99 {
100 if let Ok(hwcap2) = getauxval(AT_HWCAP2) {
101 // Zero could indicate that no features were detected, but it's also used to indicate
102 // an error. In particular, on many platforms AT_HWCAP2 will be legitimately zero,
103 // since it contains the most recent feature flags. Use the fallback only if no
104 // features were detected at all.
105 if hwcap != 0 || hwcap2 != 0 {
106 return Ok(AuxVec { hwcap, hwcap2 });
107 }
108 }
109 }
110
111 // Intentionnaly not used
112 let _ = hwcap;
113 }
114
115 // If calling getauxval fails, try to read the auxiliary vector from
116 // its file:
117 auxv_from_file("/proc/self/auxv").map_err(|_| ())
118}
119
120/// Tries to read the `key` from the auxiliary vector by calling the
121/// `getauxval` function. If the function is not linked, this function return `Err`.
122fn getauxval(key: usize) -> Result<usize, ()> {
123 type F = unsafe extern "C" fn(libc::c_ulong) -> libc::c_ulong;
124 cfg_select! {
125 any(
126 all(
127 target_os = "linux",
128 any(target_env = "gnu", target_env = "musl", target_env = "ohos"),
129 ),
130 target_os = "android",
131 ) => {
132 let ffi_getauxval: F = libc::getauxval;
133 }
134 _ => {
135 let ffi_getauxval: F = unsafe {
136 let ptr = libc::dlsym(libc::RTLD_DEFAULT, c"getauxval".as_ptr());
137 if ptr.is_null() {
138 return Err(());
139 }
140 core::mem::transmute(ptr)
141 };
142 }
143 }
144 Ok(unsafe { ffi_getauxval(key as libc::c_ulong) as usize })
145}
146
147/// Tries to read the auxiliary vector from the `file`. If this fails, this
148/// function returns `Err`.
149pub(super) fn auxv_from_file(file: &str) -> Result<AuxVec, alloc::string::String> {
150 let file = super::read_file(file)?;
151 auxv_from_file_bytes(&file)
152}
153
154/// Read auxiliary vector from a slice of bytes.
155pub(super) fn auxv_from_file_bytes(bytes: &[u8]) -> Result<AuxVec, alloc::string::String> {
156 // See <https://github.com/torvalds/linux/blob/v5.15/include/uapi/linux/auxvec.h>.
157 //
158 // The auxiliary vector contains at most 34 (key,value) fields: from
159 // `AT_MINSIGSTKSZ` to `AT_NULL`, but its number may increase.
160 let len = bytes.len();
161 let mut buf = alloc::vec![0_usize; 1 + len / core::mem::size_of::<usize>()];
162 unsafe {
163 core::ptr::copy_nonoverlapping(bytes.as_ptr(), buf.as_mut_ptr() as *mut u8, len);
164 }
165
166 auxv_from_buf(&buf)
167}
168
169/// Tries to interpret the `buffer` as an auxiliary vector. If that fails, this
170/// function returns `Err`.
171fn auxv_from_buf(buf: &[usize]) -> Result<AuxVec, alloc::string::String> {
172 // Targets with only AT_HWCAP:
173 #[cfg(any(
174 target_arch = "riscv32",
175 target_arch = "riscv64",
176 target_arch = "mips",
177 target_arch = "mips64",
178 target_arch = "loongarch32",
179 target_arch = "loongarch64",
180 ))]
181 {
182 for el in buf.chunks(2) {
183 match el[0] {
184 AT_NULL => break,
185 AT_HWCAP => return Ok(AuxVec { hwcap: el[1] }),
186 _ => (),
187 }
188 }
189 }
190 // Targets with AT_HWCAP and AT_HWCAP2:
191 #[cfg(any(
192 target_arch = "aarch64",
193 target_arch = "arm",
194 target_arch = "powerpc",
195 target_arch = "powerpc64",
196 target_arch = "s390x",
197 ))]
198 {
199 let mut hwcap = None;
200 // For some platforms, AT_HWCAP2 was added recently, so let it default to zero.
201 let mut hwcap2 = 0;
202 for el in buf.chunks(2) {
203 match el[0] {
204 AT_NULL => break,
205 AT_HWCAP => hwcap = Some(el[1]),
206 AT_HWCAP2 => hwcap2 = el[1],
207 _ => (),
208 }
209 }
210
211 if let Some(hwcap) = hwcap {
212 return Ok(AuxVec { hwcap, hwcap2 });
213 }
214 }
215 // Suppress unused variable
216 let _ = buf;
217 Err(alloc::string::String::from("hwcap not found"))
218}
219
220#[cfg(test)]
221mod tests;