]>
Commit | Line | Data |
---|---|---|
83c7162d XL |
1 | //! Parses ELF auxiliary vectors. |
2 | #![cfg_attr(not(target_arch = "aarch64"), allow(dead_code))] | |
3 | ||
4 | use mem; | |
5 | use prelude::v1::*; | |
6 | use fs::File; | |
7 | use io::Read; | |
8 | ||
9 | /// Key to access the CPU Hardware capabilities bitfield. | |
8faf50e0 | 10 | pub(crate) const AT_HWCAP: usize = 16; |
83c7162d XL |
11 | /// Key to access the CPU Hardware capabilities 2 bitfield. |
12 | #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] | |
8faf50e0 | 13 | pub(crate) const AT_HWCAP2: usize = 26; |
83c7162d XL |
14 | |
15 | /// Cache HWCAP bitfields of the ELF Auxiliary Vector. | |
16 | /// | |
17 | /// If an entry cannot be read all the bits in the bitfield are set to zero. | |
18 | /// This should be interpreted as all the features being disabled. | |
19 | #[derive(Debug, Copy, Clone)] | |
8faf50e0 | 20 | pub(crate) struct AuxVec { |
83c7162d XL |
21 | pub hwcap: usize, |
22 | #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] | |
23 | pub hwcap2: usize, | |
24 | } | |
25 | ||
26 | /// ELF Auxiliary Vector | |
27 | /// | |
28 | /// The auxiliary vector is a memory region in a running ELF program's stack | |
29 | /// composed of (key: usize, value: usize) pairs. | |
30 | /// | |
31 | /// The keys used in the aux vector are platform dependent. For Linux, they are | |
32 | /// defined in [linux/auxvec.h][auxvec_h]. The hardware capabilities of a given | |
33 | /// CPU can be queried with the `AT_HWCAP` and `AT_HWCAP2` keys. | |
34 | /// | |
35 | /// There is no perfect way of reading the auxiliary vector. | |
36 | /// | |
37 | /// - If the `getauxval` is dynamically linked to this binary, it will be used. | |
38 | /// - Otherwise, try to read `/proc/self/auxv`. | |
39 | /// - If that fails, this function returns an error. | |
40 | /// | |
41 | /// Note that run-time feature detection is not invoked for features that can | |
42 | /// be detected at compile-time. Also note that if this function returns an | |
43 | /// error, cpuinfo still can (and will) be used to try to perform run-time | |
44 | /// feature detecton on some platforms. | |
45 | /// | |
46 | /// For more information about when `getauxval` is available check the great | |
47 | /// [`auxv` crate documentation][auxv_docs]. | |
48 | /// | |
49 | /// [auxvec_h]: https://github.com/torvalds/linux/blob/master/include/uapi/linux/auxvec.h | |
50 | /// [auxv_docs]: https://docs.rs/auxv/0.3.3/auxv/ | |
8faf50e0 | 51 | pub(crate) fn auxv() -> Result<AuxVec, ()> { |
83c7162d XL |
52 | // Try to call a dynamically-linked getauxval function. |
53 | if let Ok(hwcap) = getauxval(AT_HWCAP) { | |
54 | // Targets with only AT_HWCAP: | |
55 | #[cfg(any(target_arch = "aarch64", target_arch = "mips", | |
56 | target_arch = "mips64"))] | |
57 | { | |
58 | if hwcap != 0 { | |
59 | return Ok(AuxVec { hwcap }); | |
60 | } | |
61 | } | |
62 | ||
63 | // Targets with AT_HWCAP and AT_HWCAP2: | |
64 | #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] | |
65 | { | |
66 | if let Ok(hwcap2) = getauxval(AT_HWCAP2) { | |
67 | if hwcap != 0 && hwcap2 != 0 { | |
68 | return Ok(AuxVec { hwcap, hwcap2 }); | |
69 | } | |
70 | } | |
71 | } | |
72 | drop(hwcap); | |
73 | } | |
74 | // If calling getauxval fails, try to read the auxiliary vector from | |
75 | // its file: | |
76 | auxv_from_file("/proc/self/auxv") | |
77 | } | |
78 | ||
79 | /// Tries to read the `key` from the auxiliary vector by calling the | |
80 | /// dynamically-linked `getauxval` function. If the function is not linked, | |
81 | /// this function return `Err`. | |
82 | fn getauxval(key: usize) -> Result<usize, ()> { | |
83 | use libc; | |
84 | pub type F = unsafe extern "C" fn(usize) -> usize; | |
85 | unsafe { | |
86 | let ptr = libc::dlsym( | |
87 | libc::RTLD_DEFAULT, | |
88 | "getauxval\0".as_ptr() as *const _, | |
89 | ); | |
90 | if ptr.is_null() { | |
91 | return Err(()); | |
92 | } | |
93 | ||
94 | let ffi_getauxval: F = mem::transmute(ptr); | |
95 | Ok(ffi_getauxval(key)) | |
96 | } | |
97 | } | |
98 | ||
99 | /// Tries to read the auxiliary vector from the `file`. If this fails, this | |
100 | /// function returns `Err`. | |
101 | fn auxv_from_file(file: &str) -> Result<AuxVec, ()> { | |
102 | let mut file = File::open(file).map_err(|_| ())?; | |
103 | ||
104 | // See https://github.com/torvalds/linux/blob/v3.19/include/uapi/linux/auxvec.h | |
105 | // | |
106 | // The auxiliary vector contains at most 32 (key,value) fields: from | |
107 | // `AT_EXECFN = 31` to `AT_NULL = 0`. That is, a buffer of | |
108 | // 2*32 `usize` elements is enough to read the whole vector. | |
109 | let mut buf = [0_usize; 64]; | |
110 | { | |
111 | let raw: &mut [u8; 64 * mem::size_of::<usize>()] = | |
112 | unsafe { mem::transmute(&mut buf) }; | |
113 | file.read(raw).map_err(|_| ())?; | |
114 | } | |
115 | auxv_from_buf(&buf) | |
116 | } | |
117 | ||
118 | /// Tries to interpret the `buffer` as an auxiliary vector. If that fails, this | |
119 | /// function returns `Err`. | |
120 | fn auxv_from_buf(buf: &[usize; 64]) -> Result<AuxVec, ()> { | |
121 | // Targets with only AT_HWCAP: | |
122 | #[cfg(any(target_arch = "aarch64", target_arch = "mips", | |
123 | target_arch = "mips64"))] | |
124 | { | |
125 | for el in buf.chunks(2) { | |
126 | match el[0] { | |
127 | AT_HWCAP => return Ok(AuxVec { hwcap: el[1] }), | |
128 | _ => (), | |
129 | } | |
130 | } | |
131 | } | |
132 | // Targets with AT_HWCAP and AT_HWCAP2: | |
133 | #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] | |
134 | { | |
135 | let mut hwcap = None; | |
136 | let mut hwcap2 = None; | |
137 | for el in buf.chunks(2) { | |
138 | match el[0] { | |
139 | AT_HWCAP => hwcap = Some(el[1]), | |
140 | AT_HWCAP2 => hwcap2 = Some(el[1]), | |
141 | _ => (), | |
142 | } | |
143 | } | |
144 | ||
145 | if let (Some(hwcap), Some(hwcap2)) = (hwcap, hwcap2) { | |
146 | return Ok(AuxVec { hwcap, hwcap2 }); | |
147 | } | |
148 | } | |
149 | drop(buf); | |
150 | Err(()) | |
151 | } | |
152 | ||
153 | #[cfg(test)] | |
154 | mod tests { | |
155 | extern crate auxv as auxv_crate; | |
156 | use super::*; | |
157 | ||
158 | // Reads the Auxiliary Vector key from /proc/self/auxv | |
159 | // using the auxv crate. | |
160 | fn auxv_crate_getprocfs(key: usize) -> Option<usize> { | |
161 | use self::auxv_crate::AuxvType; | |
162 | use self::auxv_crate::procfs::search_procfs_auxv; | |
163 | let k = key as AuxvType; | |
164 | match search_procfs_auxv(&[k]) { | |
165 | Ok(v) => Some(v[&k] as usize), | |
166 | Err(_) => None, | |
167 | } | |
168 | } | |
169 | ||
170 | // Reads the Auxiliary Vector key from getauxval() | |
171 | // using the auxv crate. | |
172 | #[cfg(not(any(target_arch = "mips", target_arch = "mips64")))] | |
173 | fn auxv_crate_getauxval(key: usize) -> Option<usize> { | |
174 | use self::auxv_crate::AuxvType; | |
175 | use self::auxv_crate::getauxval::Getauxval; | |
176 | let q = auxv_crate::getauxval::NativeGetauxval {}; | |
177 | match q.getauxval(key as AuxvType) { | |
178 | Ok(v) => Some(v as usize), | |
179 | Err(_) => None, | |
180 | } | |
181 | } | |
182 | ||
183 | // FIXME: on mips/mips64 getauxval returns 0, and /proc/self/auxv | |
184 | // does not always contain the AT_HWCAP key under qemu. | |
185 | #[cfg(not(any(target_arch = "mips", target_arch = "mips64", target_arch = "powerpc")))] | |
186 | #[test] | |
187 | fn auxv_crate() { | |
188 | let v = auxv(); | |
189 | if let Some(hwcap) = auxv_crate_getauxval(AT_HWCAP) { | |
190 | let rt_hwcap = v.expect("failed to find hwcap key").hwcap; | |
191 | assert_eq!(rt_hwcap, hwcap); | |
192 | } | |
193 | ||
194 | // Targets with AT_HWCAP and AT_HWCAP2: | |
195 | #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] | |
196 | { | |
197 | if let Some(hwcap2) = auxv_crate_getauxval(AT_HWCAP2) { | |
198 | let rt_hwcap2 = v.expect("failed to find hwcap2 key").hwcap2; | |
199 | assert_eq!(rt_hwcap2, hwcap2); | |
200 | } | |
201 | } | |
202 | } | |
203 | ||
204 | #[test] | |
205 | fn auxv_dump() { | |
206 | if let Ok(auxvec) = auxv() { | |
207 | println!("{:?}", auxvec); | |
208 | } else { | |
209 | println!("both getauxval() and reading /proc/self/auxv failed!"); | |
210 | } | |
211 | } | |
212 | ||
213 | cfg_if! { | |
214 | if #[cfg(target_arch = "arm")] { | |
215 | #[test] | |
216 | fn linux_rpi3() { | |
217 | let v = auxv_from_file( | |
218 | "../../stdsimd/arch/detect/test_data/linux-rpi3.auxv", | |
219 | ).unwrap(); | |
220 | assert_eq!(v.hwcap, 4174038); | |
221 | assert_eq!(v.hwcap2, 16); | |
222 | } | |
223 | ||
224 | #[test] | |
225 | #[should_panic] | |
226 | fn linux_macos_vb() { | |
227 | let _ = auxv_from_file( | |
228 | "../../stdsimd/arch/detect/test_data/macos-virtualbox-linux-x86-4850HQ.auxv" | |
229 | ).unwrap(); | |
230 | // this file is incomplete (contains hwcap but not hwcap2), we | |
231 | // want to fall back to /proc/cpuinfo in this case, so | |
232 | // reading should fail. assert_eq!(v.hwcap, 126614527); | |
233 | // assert_eq!(v.hwcap2, 0); | |
234 | } | |
235 | } else if #[cfg(target_arch = "aarch64")] { | |
236 | #[test] | |
237 | fn linux_x64() { | |
238 | let v = auxv_from_file( | |
239 | "../../stdsimd/arch/detect/test_data/linux-x64-i7-6850k.auxv", | |
240 | ).unwrap(); | |
241 | assert_eq!(v.hwcap, 3219913727); | |
242 | } | |
243 | } | |
244 | } | |
245 | ||
246 | #[test] | |
247 | fn auxv_dump_procfs() { | |
248 | if let Ok(auxvec) = auxv_from_file("/proc/self/auxv") { | |
249 | println!("{:?}", auxvec); | |
250 | } else { | |
251 | println!("reading /proc/self/auxv failed!"); | |
252 | } | |
253 | } | |
254 | ||
255 | #[test] | |
256 | fn auxv_crate_procfs() { | |
257 | let v = auxv(); | |
258 | if let Some(hwcap) = auxv_crate_getprocfs(AT_HWCAP) { | |
259 | assert_eq!(v.unwrap().hwcap, hwcap); | |
260 | } | |
261 | ||
262 | // Targets with AT_HWCAP and AT_HWCAP2: | |
263 | #[cfg(any(target_arch = "arm", target_arch = "powerpc64"))] | |
264 | { | |
265 | if let Some(hwcap2) = auxv_crate_getprocfs(AT_HWCAP2) { | |
266 | assert_eq!(v.unwrap().hwcap2, hwcap2); | |
267 | } | |
268 | } | |
269 | } | |
270 | } |