pagemap/
pagemap.rs

1use std::fmt;
2use std::fs::File;
3use std::io::{BufRead, BufReader, Read, Seek, SeekFrom};
4
5use crate::{
6    error::{PageMapError, Result},
7    kpage::KPageFlags,
8    maps::{MapsEntry, MemoryRegion},
9    page_size,
10};
11
12///////////////////////////////////////////////////////////////////////////////////////////////////
13//
14// PageMapEntry
15//
16///////////////////////////////////////////////////////////////////////////////////////////////////
17
18/// An entry read from `/proc/<PID>/pagemap` for a process, and optionally from `/proc/kpagecount`
19/// and `/proc/kpageflags` too.
20///
21/// Documentation and details about the various bits of the API can be found in Linux, at
22/// [`doc/Documentation/vm/pagemap.txt`](https://www.kernel.org/doc/Documentation/vm/pagemap.txt).
23#[derive(Debug, Clone, Copy)]
24pub struct PageMapEntry {
25    pgmap: u64,
26    kpgcn: Option<u64>,
27    kpgfl: Option<KPageFlags>,
28}
29
30impl std::convert::From<u64> for PageMapEntry {
31    fn from(pgmap: u64) -> Self {
32        PageMapEntry {
33            pgmap,
34            kpgcn: None,
35            kpgfl: None,
36        }
37    }
38}
39
40// TODO: Where to use?
41impl std::convert::From<(u64, u64, u64)> for PageMapEntry {
42    fn from((pgmap, kpgcn, kpgfl): (u64, u64, u64)) -> Self {
43        PageMapEntry {
44            pgmap,
45            kpgcn: Some(kpgcn),
46            kpgfl: Some(kpgfl.into()),
47        }
48    }
49}
50
51/// Constants are defined in Linux, at `fs/proc/task_mmu.c`.
52impl PageMapEntry {
53    ///////////////////////////////////////////////////////////////////////////////////////////
54    // pagemap constants as defined in Linux, at `fs/proc/task_mmu.c`
55    ///////////////////////////////////////////////////////////////////////////////////////////
56
57    pub const PM_PFRAME_BITS: u64 = 55;
58    pub const PM_PFRAME_MASK: u64 = (1 << Self::PM_PFRAME_BITS) - 1;
59    pub const PM_SOFT_DIRTY: u64 = 55;
60    pub const PM_MMAP_EXCLUSIVE: u64 = 56;
61    pub const PM_FILE: u64 = 61;
62    pub const PM_SWAP: u64 = 62;
63    pub const PM_PRESENT: u64 = 63;
64
65    ///////////////////////////////////////////////////////////////////////////////////////////
66    // /proc/PID/pagemap
67    ///////////////////////////////////////////////////////////////////////////////////////////
68
69    /// The raw `u64` value as read from [`procfs(5)`].
70    ///
71    /// [`procfs(5)`]: https://man7.org/linux/man-pages/man5/proc.5.html
72    #[inline(always)]
73    pub fn raw_pagemap(&self) -> u64 {
74        self.pgmap
75    }
76
77    /// Returns `true` if the [`Self::PM_PRESENT`] bit is set; `false` otherwise.
78    #[inline(always)]
79    pub fn present(&self) -> bool {
80        self.pgmap >> Self::PM_PRESENT & 1 == 1
81    }
82
83    /// Returns `true` if the [`Self::PM_SWAP`] bit is set; `false` otherwise.
84    #[inline(always)]
85    pub fn swapped(&self) -> bool {
86        self.pgmap >> Self::PM_SWAP & 1 == 1
87    }
88
89    /// Returns `true` if the [`Self::PM_FILE`] bit is set; `false` otherwise.
90    #[inline(always)]
91    pub fn file_mapped(&self) -> bool {
92        self.pgmap >> Self::PM_FILE & 1 == 1
93    }
94
95    /// Returns `true` if the [`Self::PM_FILE`] bit is clear; `false` otherwise.
96    #[inline(always)]
97    pub fn shared_anonymous(&self) -> bool {
98        self.pgmap >> Self::PM_FILE & 1 == 0
99    }
100
101    /// Returns `true` if the [`Self::PM_MMAP_EXCLUSIVE`] bit is set; `false` otherwise.
102    #[inline(always)]
103    pub fn exclusively_mapped(&self) -> bool {
104        self.pgmap >> Self::PM_MMAP_EXCLUSIVE & 1 == 1
105    }
106
107    /// Returns `true` if the [`Self::PM_SOFT_DIRTY`] bit is set; `false` otherwise.
108    #[inline(always)]
109    pub fn soft_dirty(&self) -> bool {
110        self.pgmap >> Self::PM_SOFT_DIRTY & 1 == 1
111    }
112
113    /// Returns the page frame number (decoding bits 0-54) if the [`Self::PM_PRESENT`] bit is set;
114    /// otherwise returns an error.
115    pub fn pfn(&self) -> Result<u64> {
116        if !self.present() {
117            Err(PageMapError::PageNotPresent)
118        } else {
119            Ok(self.pgmap & Self::PM_PFRAME_MASK)
120        }
121    }
122
123    /// Returns the swap type (decoding bits 0-4) if the [`Self::PM_SWAP`] bit is set; otherwise
124    /// returns an error.
125    pub fn swap_type(&self) -> Result<u8> {
126        if !self.swapped() {
127            Err(PageMapError::PageNotSwapped)
128        } else {
129            Ok((self.pgmap & 0x1fu64) as u8)
130        }
131    }
132
133    /// Returns the swap offset (decoding bits 5-55) if the [`Self::PM_SWAP`] bit is set; otherwise
134    /// returns an error.
135    pub fn swap_offset(&self) -> Result<u64> {
136        if !self.swapped() {
137            Err(PageMapError::PageNotSwapped)
138        } else {
139            Ok((self.pgmap & (0x_007f_ffff_ffff_ffe0_u64)) >> 5)
140        }
141    }
142
143    ///////////////////////////////////////////////////////////////////////////////////////////
144    // /proc/kpagecount
145    ///////////////////////////////////////////////////////////////////////////////////////////
146
147    /// The raw `u64` value as read from [`procfs(5)`], or `None` if `/proc/kpagecount` could not
148    /// be accessed.
149    ///
150    /// [`procfs(5)`]: https://man7.org/linux/man-pages/man5/proc.5.html
151    #[inline(always)]
152    pub fn kpagecount(&self) -> Option<u64> {
153        self.kpgcn
154    }
155
156    ///////////////////////////////////////////////////////////////////////////////////////////
157    // /proc/kpageflags
158    ///////////////////////////////////////////////////////////////////////////////////////////
159
160    /// The [`KPageFlags`] parsed from `/proc/kpageflags` for the page frame in this
161    /// `PageMapEntry`.
162    #[inline(always)]
163    pub fn kpageflags(&self) -> Option<KPageFlags> {
164        self.kpgfl
165    }
166
167    /// The raw `u64` value as read from [`procfs(5)`], or `None` if `/proc/kpageflags` could not
168    /// be accessed.
169    ///
170    /// [`procfs(5)`]: https://man7.org/linux/man-pages/man5/proc.5.html
171    #[inline(always)]
172    pub fn raw_kpageflags(&self) -> Option<u64> {
173        self.kpgfl.map(|kpgfl| kpgfl.bits())
174    }
175
176    fn_get_bit!(locked, KPF_LOCKED);
177    fn_get_bit!(error, KPF_ERROR);
178    fn_get_bit!(referenced, KPF_REFERENCED);
179    fn_get_bit!(uptodate, KPF_UPTODATE);
180    fn_get_bit!(dirty, KPF_DIRTY);
181    fn_get_bit!(lru, KPF_LRU);
182    fn_get_bit!(active, KPF_ACTIVE);
183    fn_get_bit!(slab, KPF_SLAB);
184    fn_get_bit!(writeback, KPF_WRITEBACK);
185    fn_get_bit!(reclaim, KPF_RECLAIM);
186    fn_get_bit!(buddy, KPF_BUDDY);
187    fn_get_bit!(mmap, KPF_MMAP);
188    fn_get_bit!(anon, KPF_ANON);
189    fn_get_bit!(swapcache, KPF_SWAPCACHE);
190    fn_get_bit!(swapbacked, KPF_SWAPBACKED);
191    fn_get_bit!(compound_head, KPF_COMPOUND_HEAD);
192    fn_get_bit!(compound_tail, KPF_COMPOUND_TAIL);
193    fn_get_bit!(huge, KPF_HUGE);
194    fn_get_bit!(unevictable, KPF_UNEVICTABLE);
195    fn_get_bit!(hwpoison, KPF_HWPOISON);
196    fn_get_bit!(nopage, KPF_NOPAGE);
197    fn_get_bit!(ksm, KPF_KSM);
198    fn_get_bit!(thp, KPF_THP);
199    fn_get_bit!(offline, KPF_OFFLINE);
200    fn_get_bit!(zero_page, KPF_ZERO_PAGE);
201    fn_get_bit!(idle, KPF_IDLE);
202    fn_get_bit!(pgtable, KPF_PGTABLE);
203}
204
205impl fmt::Display for PageMapEntry {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        match (self.present(), self.swapped()) {
208            (true, true) => unreachable!("PAGE BOTH PRESENT AND SWAPPED!"),
209            (true, false) => {
210                write!(
211                    f,
212                    "PageMapEntry{{ present: {}; swapped: {}; file_mapped: {}; exclusively_mapped: {}; soft_dirty: {}; pfn: 0x{:x} }}",
213                    self.present(), self.swapped(), self.file_mapped(), self.exclusively_mapped(),
214                    self.soft_dirty(), self.pfn().unwrap(), // Safe because self.present() == true
215                )
216            }
217            (false, true) => {
218                write!(
219                    f,
220                    "PageMapEntry{{ present: {}; swapped: {}; file_mapped: {}; exclusively_mapped: {}; soft_dirty: {}; swap_type: {}; swap_offset: 0x{:x} }}",
221                    self.present(), self.swapped(), self.file_mapped(), self.exclusively_mapped(),
222                    self.soft_dirty(), self.swap_type().unwrap(), self.swap_offset().unwrap(),
223                    // Safe to unwrap because self.swapped() == true
224                )
225            }
226            (false, false) => {
227                write!(
228                    f,
229                    "PageMapEntry{{ present: {}; swapped: {}; file_mapped: {}; exclusively_mapped: {}; soft_dirty: {} }}",
230                    self.present(), self.swapped(), self.file_mapped(), self.exclusively_mapped(),
231                    self.soft_dirty(),
232                )
233            }
234        }
235    }
236}
237
238///////////////////////////////////////////////////////////////////////////////////////////////////
239//
240// PageMap
241//
242///////////////////////////////////////////////////////////////////////////////////////////////////
243
244/// A handle used to read from:
245///
246/// - `/proc/<PID>/maps`,
247/// - `/proc/<PID>/pagemap`,
248/// - `/proc/kpagecount`, and
249/// - `/proc/kpageflags`
250///
251/// for a specific process.
252#[derive(Debug)]
253pub struct PageMap {
254    pid: u64,
255    mf: BufReader<File>,
256    pmf: File,
257    kcf: Option<File>,
258    kff: Option<File>,
259    page_size: u64,
260}
261
262impl PageMap {
263    const KPAGECOUNT: &'static str = "/proc/kpagecount";
264    const KPAGEFLAGS: &'static str = "/proc/kpageflags";
265
266    /// Construct a new `PageMap` for the process with the given `PID`.
267    pub fn new(pid: u64) -> Result<Self> {
268        let (kcf, kff) = (
269            File::open(Self::KPAGECOUNT)
270                .map_err(|e| PageMapError::Open {
271                    path: Self::KPAGECOUNT.into(),
272                    source: e,
273                })
274                .ok(),
275            File::open(Self::KPAGEFLAGS)
276                .map_err(|e| PageMapError::Open {
277                    path: Self::KPAGEFLAGS.into(),
278                    source: e,
279                })
280                .ok(),
281        );
282        let (maps_path, pagemap_path) = (
283            format!("/proc/{}/maps", pid),
284            format!("/proc/{}/pagemap", pid),
285        );
286        Ok(PageMap {
287            pid,
288            mf: BufReader::with_capacity(
289                1 << 14,
290                File::open(&maps_path).map_err(|e| PageMapError::Open {
291                    path: maps_path,
292                    source: e,
293                })?,
294            ),
295            pmf: File::open(&pagemap_path).map_err(|e| PageMapError::Open {
296                path: pagemap_path,
297                source: e,
298            })?,
299            kcf,
300            kff,
301            page_size: page_size()?,
302        })
303    }
304
305    /// Returns the `PID` of the process that this `PageMap` concerns.
306    pub fn pid(&self) -> u64 {
307        self.pid
308    }
309
310    /// Returns all virtual memory mappings for the process at hand, as parsed from
311    /// `/proc/<PID>/maps`.
312    pub fn maps(&mut self) -> Result<Vec<MapsEntry>> {
313        let pid = self.pid;
314        self.mf
315            .by_ref()
316            .lines()
317            .map(|line| {
318                line.map_err(|e| PageMapError::Read {
319                    path: format!("/proc/{}/maps", pid),
320                    source: e,
321                })?
322                .parse()
323            })
324            .collect()
325    }
326
327    /// Returns the entries parsed from reading `/proc/<PID>/pagemap` for all pages in the
328    /// specified [`MemoryRegion`] of the process at hand.
329    pub fn pagemap_region(&mut self, region: &MemoryRegion) -> Result<Vec<PageMapEntry>> {
330        let mut buf = [0; 8];
331        (region.start..region.end)
332            .step_by(self.page_size as usize)
333            .map(|addr: u64| -> Result<_> {
334                let vpn = addr / self.page_size;
335                self.pmf
336                    .seek(SeekFrom::Start(vpn * 8))
337                    .map_err(|e| PageMapError::Seek {
338                        path: format!("/proc/{}/pagemap", self.pid),
339                        source: e,
340                    })?;
341                self.pmf
342                    .read_exact(&mut buf)
343                    .map_err(|e| PageMapError::Read {
344                        path: format!("/proc/{}/pagemap", self.pid),
345                        source: e,
346                    })?;
347                Ok(u64::from_ne_bytes(buf).into())
348            })
349            .collect::<Result<_>>()
350    }
351
352    /// Returns the information about memory mappings, as parsed from reading `/proc/<PID>/maps`,
353    /// along with a `Vec<PageMapEntry>` for each of them, which represent the information read
354    /// from `/proc/<PID>/pagemap` for each contiguous page in each virtual memory region.
355    ///
356    /// If permitted, every [`PageMapEntry`] is also populated with information read from
357    /// `/proc/kpagecount` and `/proc/kpageflags`.
358    pub fn pagemap(&mut self) -> Result<Vec<(MapsEntry, Vec<PageMapEntry>)>> {
359        self.maps()?
360            .into_iter()
361            .map(|map_entry| {
362                let mut pmes = self.pagemap_region(&map_entry.region)?;
363                if self.kcf.is_some() && self.kff.is_some() {
364                    for pme in &mut pmes {
365                        if let Ok(pfn) = pme.pfn() {
366                            pme.kpgcn = Some(self.kpagecount(pfn)?);
367                            pme.kpgfl = Some(self.kpageflags(pfn)?);
368                        }
369                    }
370                }
371                Ok((map_entry, pmes))
372            })
373            .collect()
374    }
375
376    /// Attempt to read the number of times that the page with the given `PFN` is referenced, from
377    /// `/proc/kpagecount`.
378    ///
379    /// # Errors
380    ///
381    /// The method may return [`PageMapError::Read`] or [`PageMapError::Seek`] if either reading
382    /// from or seeking into `/proc/kpagecount` fails.
383    ///
384    /// Most importantly, the method may return [`PageMapError::Access`] if opening
385    /// `/proc/kpagecount` was not permitted at the time that the `PageMapEntry` was instantiated.
386    pub fn kpagecount(&self, pfn: u64) -> Result<u64> {
387        let mut buf = [0; 8];
388        let mut kcf = self
389            .kcf
390            .as_ref()
391            .ok_or_else(|| PageMapError::Access(Self::KPAGECOUNT.into()))?;
392        kcf.seek(SeekFrom::Start(pfn * 8))
393            .map_err(|e| PageMapError::Seek {
394                path: Self::KPAGECOUNT.into(),
395                source: e,
396            })?;
397        kcf.read_exact(&mut buf).map_err(|e| PageMapError::Read {
398            path: Self::KPAGECOUNT.into(),
399            source: e,
400        })?;
401        Ok(u64::from_ne_bytes(buf))
402    }
403
404    /// Attempt to read the flags for the page with the given `PFN` from `/proc/kpageflags`.
405    ///
406    /// # Errors
407    ///
408    /// The method may return [`PageMapError::Read`] or [`PageMapError::Seek`] if either reading
409    /// from or seeking into `/proc/kpageflags` fails.
410    ///
411    /// Most importantly, the method may return [`PageMapError::Access`] if opening
412    /// `/proc/kpageflags` was not permitted at the time that the `PageMapEntry` was instantiated.
413    pub fn kpageflags(&self, pfn: u64) -> Result<KPageFlags> {
414        let mut buf = [0; 8];
415        let mut kff = self
416            .kff
417            .as_ref()
418            .ok_or_else(|| PageMapError::Access(Self::KPAGEFLAGS.into()))?;
419        kff.seek(SeekFrom::Start(pfn * 8))
420            .map_err(|e| PageMapError::Seek {
421                path: Self::KPAGEFLAGS.into(),
422                source: e,
423            })?;
424        kff.read_exact(&mut buf).map_err(|e| PageMapError::Read {
425            path: Self::KPAGEFLAGS.into(),
426            source: e,
427        })?;
428        Ok(KPageFlags::from_bits_truncate(u64::from_ne_bytes(buf)))
429    }
430}