-
Notifications
You must be signed in to change notification settings - Fork 129
add /proc/kpageflags #233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
add /proc/kpageflags #233
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| // | ||
| // Look for a value in the virtual memory of a process, and physical memory, then prints memory page details | ||
| // This shows how to go from virtual address to mapping, and from mapping to physical address. | ||
| // | ||
| // This requires CAP_SYS_ADMIN privilege, or root | ||
| // | ||
| // Sample output: | ||
| // | ||
| // Virtual address of `variable`: 0x7ffd2de4708f | ||
| // Found memory mapping | ||
| // MemoryMap { address: (140725373272064, 140725373407232), perms: "rw-p", offset: 0, dev: (0, 0), inode: 0, pathname: Stack } | ||
| // Found page | ||
| // virt_mem: 0x7ffd2de47000, pfn: 0x107b06, phys_addr: 0x107b06000, flags: UPTODATE | LRU | MMAP | ANON | SWAPBACKED | ||
| // | ||
|
|
||
| use procfs::process::Process; | ||
| use procfs::KPageFlags; | ||
|
|
||
| fn main() { | ||
| if !rustix::process::geteuid().is_root() { | ||
| // KpageFlags::new().unwrap() will panic either way | ||
| panic!("ERROR: Access to /proc/kpageflags requires root, re-run with sudo"); | ||
| } | ||
|
|
||
| let page_size = procfs::page_size().unwrap(); | ||
|
|
||
| // We will inspect this process's own memory | ||
| let process = Process::myself().expect("Unable to load myself!"); | ||
| let mut kpageflags = KPageFlags::new().expect("Can't open /proc/kpageflags"); | ||
|
|
||
| let mut pagemap = process.pagemap().unwrap(); | ||
|
|
||
| // The memory maps are read now, so the value we look for must already exist in RAM when we it this line | ||
| // In this case it works, because the variables already exist in the executable | ||
| // You probably want to put this right above the "for memory_map" loop | ||
| let mem_map = process.maps().unwrap(); | ||
|
|
||
| // We allocate memory for a value. This is a trick to get a semi random value | ||
| // The goal is to find this value in physical memory | ||
| let chrono = std::time::Instant::now(); | ||
| let variable: u8 = chrono.elapsed().as_nanos() as u8; | ||
|
|
||
| // We could do the same with a constant, the compiler will place this value in a different memory mapping with different properties | ||
| //let constant = 42u8; | ||
|
|
||
| // `ptr` is the virtual address we are looking for | ||
| let ptr = &variable as *const u8; | ||
| println!("Virtual address of `variable`: {:p}", ptr); | ||
|
|
||
| for memory_map in mem_map { | ||
| let mem_start = memory_map.address.0; | ||
| let mem_end = memory_map.address.1; | ||
|
|
||
| if (ptr as u64) < mem_start || (ptr as u64) >= mem_end { | ||
| // pointer is not in this memory mapping | ||
| continue; | ||
| } | ||
|
|
||
| // found the memory mapping where the value is stored | ||
| println!("Found memory mapping\n{:?}", memory_map); | ||
|
|
||
| // memory is split into pages (usually 4 kiB) | ||
| let index_start = (mem_start / page_size) as usize; | ||
| let index_end = (mem_end / page_size) as usize; | ||
|
|
||
| for index in index_start..index_end { | ||
| // we search for the exact page inside the memory mapping | ||
| let virt_mem = index * page_size as usize; | ||
|
|
||
| // ptr must be reside between this page and the next one | ||
| if (ptr as usize) < virt_mem || (ptr as usize) >= virt_mem + page_size as usize { | ||
| continue; | ||
| } | ||
|
|
||
| // we found the exact page where the value resides | ||
| let page_info = pagemap.get_info(index).unwrap(); | ||
| match page_info { | ||
| procfs::process::PageInfo::MemoryPage(memory_page) => { | ||
| let pfn = memory_page.get_page_frame_number(); | ||
| let phys_addr = pfn * page_size; | ||
|
|
||
| let physical_page_info = kpageflags.get_info(pfn).expect("Can't get kpageflags info"); | ||
|
|
||
| println!( | ||
| "Found page\nvirt_mem: 0x{:x}, pfn: 0x{:x}, phys_addr: 0x{:x}, flags: {:?}", | ||
| virt_mem, pfn, phys_addr, physical_page_info | ||
| ); | ||
| } | ||
| procfs::process::PageInfo::SwapPage(_) => (), // page is in swap | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| use crate::{FileWrapper, ProcResult}; | ||
|
|
||
| use bitflags::bitflags; | ||
| use std::{ | ||
| io::{BufReader, Read, Seek, SeekFrom}, | ||
| mem::size_of, | ||
| ops::{Bound, RangeBounds}, | ||
| path::Path, | ||
| }; | ||
|
|
||
| #[cfg(feature = "serde1")] | ||
| use serde::{Deserialize, Serialize}; | ||
|
|
||
| //const fn genmask(high: usize, low: usize) -> u64 { | ||
| // let mask_bits = size_of::<u64>() * 8; | ||
| // (!0 - (1 << low) + 1) & (!0 >> (mask_bits - 1 - high)) | ||
| //} | ||
|
|
||
| bitflags! { | ||
| /// Represents the fields and flags in a page table entry for a memory page. | ||
| #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] | ||
| pub struct PhysicalPageFlags: u64 { | ||
| /// The page is being locked for exclusive access, e.g. by undergoing read/write IO | ||
| const LOCKED = 1 << 0; | ||
| /// IO error occurred | ||
| const ERROR = 1 << 1; | ||
| /// The page has been referenced since last LRU list enqueue/requeue | ||
| const REFERENCED = 1 << 2; | ||
| /// The page has up-to-date data. ie. for file backed page: (in-memory data revision >= on-disk one) | ||
| const UPTODATE = 1 << 3; | ||
| /// The page has been written to, hence contains new data. i.e. for file backed page: (in-memory data revision > on-disk one) | ||
| const DIRTY = 1 << 4; | ||
| /// The page is in one of the LRU lists | ||
| const LRU = 1 << 5; | ||
| /// The page is in the active LRU list | ||
| const ACTIVE = 1 << 6; | ||
| /// The page is managed by the SLAB/SLOB/SLUB/SLQB kernel memory allocator. When compound page is used, SLUB/SLQB will only set this flag on the head page; SLOB will not flag it at all | ||
| const SLAB = 1 << 7; | ||
| /// The page is being synced to disk | ||
| const WRITEBACK = 1 << 8; | ||
| /// The page will be reclaimed soon after its pageout IO completed | ||
| const RECLAIM = 1 << 9; | ||
| /// A free memory block managed by the buddy system allocator. The buddy system organizes free memory in blocks of various orders. An order N block has 2^N physically contiguous pages, with the BUDDY flag set for and _only_ for the first page | ||
| const BUDDY = 1 << 10; | ||
| /// A memory mapped page | ||
| const MMAP = 1 << 11; | ||
| /// A memory mapped page that is not part of a file | ||
| const ANON = 1 << 12; | ||
| /// The page is mapped to swap space, i.e. has an associated swap entry | ||
| const SWAPCACHE = 1 << 13; | ||
| /// The page is backed by swap/RAM | ||
| const SWAPBACKED = 1 << 14; | ||
| /// A compound page with order N consists of 2^N physically contiguous pages. A compound page with order 2 takes the form of “HTTT”, where H donates its head page and T donates its tail page(s). The major consumers of compound pages are hugeTLB pages (<https://www.kernel.org/doc/html/latest/admin-guide/mm/hugetlbpage.html#hugetlbpage>), the SLUB etc. memory allocators and various device drivers. However in this interface, only huge/giga pages are made visible to end users | ||
| const COMPOUND_HEAD = 1 << 15; | ||
| /// A compound page tail (see description above) | ||
| const COMPOUND_TAIL = 1 << 16; | ||
| /// This is an integral part of a HugeTLB page | ||
| const HUGE = 1 << 17; | ||
| /// The page is in the unevictable (non-)LRU list It is somehow pinned and not a candidate for LRU page reclaims, e.g. ramfs pages, shmctl(SHM_LOCK) and mlock() memory segments | ||
| const UNEVICTABLE = 1 << 18; | ||
| /// Hardware detected memory corruption on this page: don’t touch the data! | ||
| const HWPOISON = 1 << 19; | ||
| /// No page frame exists at the requested address | ||
| const NOPAGE = 1 << 20; | ||
| /// Identical memory pages dynamically shared between one or more processes | ||
| const KSM = 1 << 21; | ||
| /// Contiguous pages which construct transparent hugepages | ||
| const THP = 1 << 22; | ||
| /// The page is logically offline | ||
| const OFFLINE = 1 << 23; | ||
| /// Zero page for pfn_zero or huge_zero page | ||
| const ZERO_PAGE = 1 << 24; | ||
| /// The page has not been accessed since it was marked idle (see <https://www.kernel.org/doc/html/latest/admin-guide/mm/idle_page_tracking.html#idle-page-tracking>). Note that this flag may be stale in case the page was accessed via a PTE. To make sure the flag is up-to-date one has to read /sys/kernel/mm/page_idle/bitmap first | ||
| const IDLE = 1 << 25; | ||
| /// The page is in use as a page table | ||
| const PGTABLE = 1 << 26; | ||
|
|
||
| } | ||
| } | ||
|
|
||
| impl PhysicalPageFlags { | ||
| pub(crate) fn parse_info(info: u64) -> Self { | ||
| PhysicalPageFlags::from_bits_truncate(info) | ||
| } | ||
| } | ||
|
|
||
| /// Parse physical memory flags accessing `/proc/kpageflags`. | ||
| /// | ||
| /// Require root or CAP_SYS_ADMIN | ||
| pub struct KPageFlags { | ||
| reader: BufReader<FileWrapper>, | ||
| } | ||
|
|
||
| impl KPageFlags { | ||
| /// Get a parser from default `/proc/kpageflags` | ||
| /// | ||
| /// Return `Err` if process is not running as root or don't have CAP_SYS_ADMIN | ||
| pub fn new() -> ProcResult<Self> { | ||
| Self::from_custom_root("/proc") | ||
| } | ||
|
|
||
| /// Get a parser from custom `/proc` | ||
| /// | ||
| /// Return `Err` if process is not running as root or don't have CAP_SYS_ADMIN | ||
| pub fn from_custom_root<P: AsRef<Path>>(root: P) -> ProcResult<Self> { | ||
| let mut path = root.as_ref().to_path_buf(); | ||
| path.push("kpageflags"); | ||
|
|
||
| let reader = BufReader::new(FileWrapper::open(path)?); | ||
|
|
||
| Ok(Self { reader }) | ||
| } | ||
|
|
||
| /// Retrieve information in the page table entry for the PFN (page frame number) at index `page_index`. | ||
| /// If you need to retrieve multiple PFNs, opt for [Self::get_range_info()] instead. | ||
| /// | ||
| /// Return Err if the PFN is not in RAM (see [crate::iomem()]): | ||
| /// Io(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" }, None) | ||
| pub fn get_info(&mut self, page_index: u64) -> ProcResult<PhysicalPageFlags> { | ||
| self.get_range_info(page_index..page_index + 1) | ||
| .map(|mut vec| vec.pop().unwrap()) | ||
| } | ||
|
|
||
| /// Retrieve information in the page table entry for the PFNs within range `page_range`. | ||
| /// | ||
| /// Return Err if any PFN is not in RAM (see [crate::iomem()]): | ||
| /// Io(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" }, None) | ||
| pub fn get_range_info(&mut self, page_range: impl RangeBounds<u64>) -> ProcResult<Vec<PhysicalPageFlags>> { | ||
| // `start` is always included | ||
| let start = match page_range.start_bound() { | ||
| Bound::Included(v) => *v, | ||
| Bound::Excluded(v) => *v + 1, | ||
| Bound::Unbounded => 0, | ||
| }; | ||
|
|
||
| // `end` is always excluded | ||
| let end = match page_range.end_bound() { | ||
| Bound::Included(v) => *v + 1, | ||
| Bound::Excluded(v) => *v, | ||
| Bound::Unbounded => std::u64::MAX / crate::page_size().unwrap(), | ||
| }; | ||
|
|
||
| let start_position = start * size_of::<u64>() as u64; | ||
| self.reader.seek(SeekFrom::Start(start_position))?; | ||
|
|
||
| let mut page_infos = Vec::with_capacity((end - start) as usize); | ||
| for _ in start..end { | ||
| let mut info_bytes = [0; size_of::<u64>()]; | ||
| self.reader.read_exact(&mut info_bytes)?; | ||
| page_infos.push(PhysicalPageFlags::parse_info(u64::from_ne_bytes(info_bytes))); | ||
| } | ||
|
|
||
| Ok(page_infos) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_kpageflags_parsing() { | ||
| let pagemap_entry: u64 = 0b0000000000000000000000000000000000000000000000000000000000000001; | ||
| let info = PhysicalPageFlags::parse_info(pagemap_entry); | ||
| assert!(info == PhysicalPageFlags::LOCKED); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for adding all these nice comments on these bitflag values