blazesym/normalize/
ioctl.rs1use std::borrow::Cow;
2use std::ffi::c_int;
3use std::fs::File;
4use std::io;
5use std::mem::size_of;
6use std::mem::size_of_val;
7use std::mem::MaybeUninit;
8
9use libc::ENOENT;
10use libc::ENOTTY;
11
12use crate::maps::MapsEntry;
13use crate::maps::Perm;
14use crate::Addr;
15use crate::Error;
16use crate::ErrorExt as _;
17use crate::ErrorKind;
18use crate::Pid;
19use crate::Result;
20
21const PROCMAP_QUERY: usize = 0xC0686611; #[allow(non_camel_case_types)]
25type procmap_query_flags = c_int;
26
27const PROCMAP_QUERY_VMA_READABLE: procmap_query_flags = 0x01;
28const PROCMAP_QUERY_VMA_WRITABLE: procmap_query_flags = 0x02;
29const PROCMAP_QUERY_VMA_EXECUTABLE: procmap_query_flags = 0x04;
30#[cfg(test)]
31const PROCMAP_QUERY_VMA_SHARED: procmap_query_flags = 0x08;
32const PROCMAP_QUERY_COVERING_OR_NEXT_VMA: procmap_query_flags = 0x10;
33
34
35#[allow(non_camel_case_types)]
36#[repr(C)]
37#[derive(Clone, Default)]
38struct procmap_query {
39 size: u64,
41 query_flags: u64, query_addr: u64, vma_start: u64, vma_end: u64, vma_flags: u64, vma_page_size: u64, vma_offset: u64, inode: u64, dev_major: u32, dev_minor: u32, vma_name_size: u32, build_id_size: u32, vma_name_addr: u64, build_id_addr: u64, }
120
121
122fn vma_flags_to_perm(vma_flags: u64) -> Perm {
123 let vma_flags = vma_flags as i32;
124 let mut perm = Perm::default();
125
126 if vma_flags & PROCMAP_QUERY_VMA_READABLE != 0 {
127 perm |= Perm::R;
128 }
129 if vma_flags & PROCMAP_QUERY_VMA_WRITABLE != 0 {
130 perm |= Perm::W;
131 }
132 if vma_flags & PROCMAP_QUERY_VMA_EXECUTABLE != 0 {
133 perm |= Perm::X;
134 }
135 perm
136}
137
138
139#[cfg(linux)]
143pub(crate) fn query_procmap(
144 file: &File,
145 pid: Pid,
146 addr: Addr,
147 build_id: bool,
148) -> Result<Option<MapsEntry>> {
149 use libc::ioctl;
150 use std::os::unix::io::AsFd as _;
151 use std::os::unix::io::AsRawFd as _;
152
153 use crate::maps::parse_path_name;
154
155 let mut path_buf = MaybeUninit::<[u8; 4096]>::uninit();
156 let mut build_id_buf = MaybeUninit::<[u8; 56]>::uninit();
157 let mut query = procmap_query {
158 size: size_of::<procmap_query>() as _,
159 query_flags: (PROCMAP_QUERY_COVERING_OR_NEXT_VMA
160 | PROCMAP_QUERY_VMA_READABLE) as _,
167 query_addr: addr,
168 vma_name_addr: path_buf.as_mut_ptr() as _,
169 vma_name_size: size_of_val(&path_buf) as _,
170 build_id_addr: if build_id {
171 build_id_buf.as_mut_ptr() as _
172 } else {
173 0
174 },
175 build_id_size: if build_id {
176 size_of_val(&build_id_buf) as _
177 } else {
178 0
179 },
180 ..Default::default()
181 };
182
183 let rc = unsafe {
186 ioctl(
187 file.as_fd().as_raw_fd(),
188 PROCMAP_QUERY as _,
189 &mut query as *mut procmap_query,
190 )
191 };
192 if rc < 0 {
193 let err = io::Error::last_os_error();
194 match err.raw_os_error() {
195 Some(e) if e == ENOTTY => {
196 return Err(Error::with_unsupported("PROCMAP_QUERY is not supported"))
197 }
198 Some(e) if e == ENOENT => return Ok(None),
199 _ => (),
200 }
201 return Err(Error::from(err))
202 }
203
204 let path_buf = unsafe { path_buf.assume_init_ref() };
206 let path = &path_buf[0..query.vma_name_size.saturating_sub(1) as usize];
207 let path_name = parse_path_name(path, pid, query.vma_start, query.vma_end)?;
208
209 let mut entry = MapsEntry {
210 range: query.vma_start..query.vma_end,
211 perm: vma_flags_to_perm(query.vma_flags),
212 offset: query.vma_offset,
213 path_name,
214 build_id: None,
215 };
216
217 if build_id && query.build_id_size > 0 {
218 let build_id_buf = unsafe { build_id_buf.assume_init_ref() };
221 let build_id = build_id_buf[0..query.build_id_size as usize].to_vec();
222 entry.build_id = Some(Cow::Owned(build_id));
223 }
224 Ok(Some(entry))
225}
226
227#[cfg(not(linux))]
228pub(crate) fn query_procmap(
229 _file: &File,
230 _pid: Pid,
231 _addr: Addr,
232 _build_id: bool,
233) -> Result<Option<MapsEntry>> {
234 unimplemented!()
235}
236
237
238pub fn is_procmap_query_supported() -> Result<bool> {
240 let pid = Pid::Slf;
241 let path = format!("/proc/{pid}/maps");
242 let file = File::open(&path).with_context(|| format!("failed to open `{path}` for reading"))?;
243 let addr = 0;
244 let build_ids = false;
245
246 let result = query_procmap(&file, pid, addr, build_ids);
247 match result {
248 Ok(..) => Ok(true),
249 Err(err) if err.kind() == ErrorKind::Unsupported => Ok(false),
250 Err(err) => Err(err),
251 }
252}
253
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 use std::env::current_exe;
260 use std::fs::File;
261 use std::thread::sleep;
262 use std::time::Duration;
263
264 use crate::maps;
265
266 use super::super::buildid::read_elf_build_id;
267
268
269 #[test]
271 fn vma_flags_conversion() {
272 let flags = 0;
273 assert_eq!(vma_flags_to_perm(flags as _), Perm::default());
274
275 let flags = PROCMAP_QUERY_VMA_READABLE;
276 assert_eq!(vma_flags_to_perm(flags as _), Perm::R);
277
278 let flags = PROCMAP_QUERY_VMA_READABLE | PROCMAP_QUERY_VMA_WRITABLE;
279 assert_eq!(vma_flags_to_perm(flags as _), Perm::RW);
280
281 let flags = PROCMAP_QUERY_VMA_EXECUTABLE | PROCMAP_QUERY_VMA_SHARED;
282 assert_eq!(vma_flags_to_perm(flags as _), Perm::X);
283
284 let flags = PROCMAP_QUERY_COVERING_OR_NEXT_VMA | PROCMAP_QUERY_VMA_EXECUTABLE;
285 assert_eq!(vma_flags_to_perm(flags as _), Perm::X);
286 }
287
288 #[test]
291 fn procmap_query_supported() {
292 let _supported = is_procmap_query_supported().unwrap();
293 }
294
295 #[test]
298 #[ignore = "test requires PROCMAP_QUERY ioctl kernel support"]
299 fn invalid_vma_querying_ioctl() {
300 let pid = Pid::Slf;
301 let path = format!("/proc/{pid}/maps");
302 let file = File::open(path).unwrap();
303 let addr = 0xfffffffff000;
304 let result = query_procmap(&file, pid, addr, false).unwrap();
305 assert_eq!(result, None);
306 }
307
308 #[test]
311 #[ignore = "test requires PROCMAP_QUERY ioctl kernel support"]
312 fn valid_vma_querying_ioctl() {
313 fn test(build_ids: bool) {
314 let pid = Pid::Slf;
315 let path = format!("/proc/{pid}/maps");
316 let file = File::open(path).unwrap();
317 let addr = valid_vma_querying_ioctl as Addr;
318 let entry = query_procmap(&file, pid, addr, build_ids).unwrap().unwrap();
319 assert!(
320 entry.range.contains(&addr),
321 "{:#x?} : {addr:#x}",
322 entry.range
323 );
324 assert_eq!(entry.perm, Perm::RX);
327 let exe = current_exe().unwrap();
328 assert_eq!(
329 entry
330 .path_name
331 .as_ref()
332 .unwrap()
333 .as_path()
334 .unwrap()
335 .symbolic_path,
336 exe
337 );
338
339 if build_ids {
340 let build_id = read_elf_build_id(&exe).unwrap();
341 assert_eq!(entry.build_id, build_id);
342 } else {
343 assert_eq!(entry.build_id, None);
344 }
345 }
346
347 test(false);
348 test(true);
349 }
350
351 #[test]
354 #[ignore = "test requires PROCMAP_QUERY ioctl kernel support"]
355 fn vma_comparison() {
356 fn parse_maps(pid: Pid, from_text: &mut Vec<MapsEntry>) {
357 let () = from_text.clear();
358
359 let it = maps::parse_filtered(pid).unwrap();
360 for result in it {
361 let vma = result.unwrap();
362 let () = from_text.push(vma);
363 }
364 }
365
366 fn parse_ioctl(pid: Pid, from_ioctl: &mut Vec<MapsEntry>) {
367 let () = from_ioctl.clear();
368
369 let path = format!("/proc/{pid}/maps");
370 let file = File::open(path).unwrap();
371 let build_ids = false;
372 let mut next_addr = 0;
373 while let Some(entry) = query_procmap(&file, pid, next_addr, build_ids).unwrap() {
374 next_addr = entry.range.end;
375 if maps::filter_relevant(&entry) {
376 let () = from_ioctl.push(entry);
377 }
378 }
379 }
380
381 let pid = Pid::Slf;
382 let mut from_text = Vec::new();
383 let mut from_ioctl = Vec::new();
384
385 for _ in 0..5 {
389 let () = parse_maps(pid, &mut from_text);
390 let () = parse_ioctl(pid, &mut from_ioctl);
391
392 if from_text == from_ioctl {
393 break
394 }
395
396 sleep(Duration::from_millis(500));
397 }
398
399 assert_eq!(from_text, from_ioctl, "{from_text:#x?}\n{from_ioctl:#x?}");
400 }
401}