use std::borrow::Cow;
use std::ffi::c_int;
use std::fs::File;
use std::io;
use std::mem::size_of;
use std::mem::size_of_val;
use std::mem::MaybeUninit;
use libc::ENOENT;
use libc::ENOTTY;
use crate::maps::MapsEntry;
use crate::maps::Perm;
use crate::Addr;
use crate::Error;
use crate::ErrorExt as _;
use crate::ErrorKind;
use crate::Pid;
use crate::Result;
const PROCMAP_QUERY: usize = 0xC0686611;
#[allow(non_camel_case_types)]
type procmap_query_flags = c_int;
const PROCMAP_QUERY_VMA_READABLE: procmap_query_flags = 0x01;
const PROCMAP_QUERY_VMA_WRITABLE: procmap_query_flags = 0x02;
const PROCMAP_QUERY_VMA_EXECUTABLE: procmap_query_flags = 0x04;
#[cfg(test)]
const PROCMAP_QUERY_VMA_SHARED: procmap_query_flags = 0x08;
const PROCMAP_QUERY_COVERING_OR_NEXT_VMA: procmap_query_flags = 0x10;
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Clone, Default)]
struct procmap_query {
size: u64,
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,
}
fn vma_flags_to_perm(vma_flags: u64) -> Perm {
let vma_flags = vma_flags as i32;
let mut perm = Perm::default();
if vma_flags & PROCMAP_QUERY_VMA_READABLE != 0 {
perm |= Perm::R;
}
if vma_flags & PROCMAP_QUERY_VMA_WRITABLE != 0 {
perm |= Perm::W;
}
if vma_flags & PROCMAP_QUERY_VMA_EXECUTABLE != 0 {
perm |= Perm::X;
}
perm
}
#[cfg(linux)]
pub(crate) fn query_procmap(
file: &File,
pid: Pid,
addr: Addr,
build_id: bool,
) -> Result<Option<MapsEntry>> {
use libc::ioctl;
use std::os::unix::io::AsFd as _;
use std::os::unix::io::AsRawFd as _;
use crate::maps::parse_path_name;
let mut path_buf = MaybeUninit::<[u8; 4096]>::uninit();
let mut build_id_buf = MaybeUninit::<[u8; 56]>::uninit();
let mut query = procmap_query {
size: size_of::<procmap_query>() as _,
query_flags: (PROCMAP_QUERY_COVERING_OR_NEXT_VMA
| PROCMAP_QUERY_VMA_READABLE) as _,
query_addr: addr,
vma_name_addr: path_buf.as_mut_ptr() as _,
vma_name_size: size_of_val(&path_buf) as _,
build_id_addr: if build_id {
build_id_buf.as_mut_ptr() as _
} else {
0
},
build_id_size: if build_id {
size_of_val(&build_id_buf) as _
} else {
0
},
..Default::default()
};
let rc = unsafe {
ioctl(
file.as_fd().as_raw_fd(),
PROCMAP_QUERY as _,
&mut query as *mut procmap_query,
)
};
if rc < 0 {
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(e) if e == ENOTTY => {
return Err(Error::with_unsupported("PROCMAP_QUERY is not supported"))
}
Some(e) if e == ENOENT => return Ok(None),
_ => (),
}
return Err(Error::from(err))
}
let path_buf = unsafe { path_buf.assume_init_ref() };
let path = &path_buf[0..query.vma_name_size.saturating_sub(1) as usize];
let path_name = parse_path_name(path, pid, query.vma_start, query.vma_end)?;
let mut entry = MapsEntry {
range: query.vma_start..query.vma_end,
perm: vma_flags_to_perm(query.vma_flags),
offset: query.vma_offset,
path_name,
build_id: None,
};
if build_id && query.build_id_size > 0 {
let build_id_buf = unsafe { build_id_buf.assume_init_ref() };
let build_id = build_id_buf[0..query.build_id_size as usize].to_vec();
entry.build_id = Some(Cow::Owned(build_id));
}
Ok(Some(entry))
}
#[cfg(not(linux))]
pub(crate) fn query_procmap(
_file: &File,
_pid: Pid,
_addr: Addr,
_build_id: bool,
) -> Result<Option<MapsEntry>> {
unimplemented!()
}
pub fn is_procmap_query_supported() -> Result<bool> {
let pid = Pid::Slf;
let path = format!("/proc/{pid}/maps");
let file = File::open(&path).with_context(|| format!("failed to open `{path}` for reading"))?;
let addr = 0;
let build_ids = false;
let result = query_procmap(&file, pid, addr, build_ids);
match result {
Ok(..) => Ok(true),
Err(err) if err.kind() == ErrorKind::Unsupported => Ok(false),
Err(err) => Err(err),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env::current_exe;
use std::fs::File;
use std::thread::sleep;
use std::time::Duration;
use crate::maps;
use super::super::buildid::read_elf_build_id;
#[test]
fn vma_flags_conversion() {
let flags = 0;
assert_eq!(vma_flags_to_perm(flags as _), Perm::default());
let flags = PROCMAP_QUERY_VMA_READABLE;
assert_eq!(vma_flags_to_perm(flags as _), Perm::R);
let flags = PROCMAP_QUERY_VMA_READABLE | PROCMAP_QUERY_VMA_WRITABLE;
assert_eq!(vma_flags_to_perm(flags as _), Perm::RW);
let flags = PROCMAP_QUERY_VMA_EXECUTABLE | PROCMAP_QUERY_VMA_SHARED;
assert_eq!(vma_flags_to_perm(flags as _), Perm::X);
let flags = PROCMAP_QUERY_COVERING_OR_NEXT_VMA | PROCMAP_QUERY_VMA_EXECUTABLE;
assert_eq!(vma_flags_to_perm(flags as _), Perm::X);
}
#[test]
fn procmap_query_supported() {
let _supported = is_procmap_query_supported().unwrap();
}
#[test]
#[ignore = "test requires PROCMAP_QUERY ioctl kernel support"]
fn invalid_vma_querying_ioctl() {
let pid = Pid::Slf;
let path = format!("/proc/{pid}/maps");
let file = File::open(path).unwrap();
let addr = 0xfffffffff000;
let result = query_procmap(&file, pid, addr, false).unwrap();
assert_eq!(result, None);
}
#[test]
#[ignore = "test requires PROCMAP_QUERY ioctl kernel support"]
fn valid_vma_querying_ioctl() {
fn test(build_ids: bool) {
let pid = Pid::Slf;
let path = format!("/proc/{pid}/maps");
let file = File::open(path).unwrap();
let addr = valid_vma_querying_ioctl as *const () as Addr;
let entry = query_procmap(&file, pid, addr, build_ids).unwrap().unwrap();
assert!(
entry.range.contains(&addr),
"{:#x?} : {addr:#x}",
entry.range
);
assert_eq!(entry.perm, Perm::RX);
let exe = current_exe().unwrap();
assert_eq!(
entry
.path_name
.as_ref()
.unwrap()
.as_path()
.unwrap()
.symbolic_path,
exe
);
if build_ids {
let build_id = read_elf_build_id(&exe).unwrap();
assert_eq!(entry.build_id, build_id);
} else {
assert_eq!(entry.build_id, None);
}
}
test(false);
test(true);
}
#[test]
#[ignore = "test requires PROCMAP_QUERY ioctl kernel support"]
fn vma_comparison() {
fn parse_maps(pid: Pid, from_text: &mut Vec<MapsEntry>) {
let () = from_text.clear();
let it = maps::parse_filtered(pid).unwrap();
for result in it {
let vma = result.unwrap();
let () = from_text.push(vma);
}
}
fn parse_ioctl(pid: Pid, from_ioctl: &mut Vec<MapsEntry>) {
let () = from_ioctl.clear();
let path = format!("/proc/{pid}/maps");
let file = File::open(path).unwrap();
let build_ids = false;
let mut next_addr = 0;
while let Some(entry) = query_procmap(&file, pid, next_addr, build_ids).unwrap() {
next_addr = entry.range.end;
if maps::filter_relevant(&entry) {
let () = from_ioctl.push(entry);
}
}
}
let pid = Pid::Slf;
let mut from_text = Vec::new();
let mut from_ioctl = Vec::new();
for _ in 0..5 {
let () = parse_maps(pid, &mut from_text);
let () = parse_ioctl(pid, &mut from_ioctl);
if from_text == from_ioctl {
break
}
sleep(Duration::from_millis(500));
}
assert_eq!(from_text, from_ioctl, "{from_text:#x?}\n{from_ioctl:#x?}");
}
}