Home / Alt manpages / proc_pid_pagemap(5)

  • proc_pid_pagemap(5)
  • File format
  • linux

Read Linux Virtual Page Mappings with /proc/pid/pagemap

You will inspect one process virtual page, read its 64-bit pagemap entry, and decode the bits that say whether the page is resident, swapped, file-backed or soft-dirty. The method is read-only. It does not alter the target process or its memory. Allow about 15 minutes if Python 3 is already installed.

This guide follows the local proc_pid_pagemap(5) page from Linux man-pages 6.7, on a machine currently running Linux 6.8.0. The file has existed since Linux 2.6.25, but newer flag meanings are versioned below. You need a shell, Python 3 for the worked probe, and a process that you are allowed to inspect.

1. Check the interface and page size

Start by confirming that the kernel exposes the file and record the system page size. Use an ordinary shell for this check:

$ test -r /proc/self/pagemap && echo 'pagemap is readable'
pagemap is readable
$ getconf PAGESIZE
4096

The first command checks the current shell's view of the file. The page is present only when the kernel was built with CONFIG_PROC_PAGE_MONITOR. A missing file is therefore a kernel configuration or procfs problem, not a malformed offset.

Checkpoint: keep the page-size value. Every pagemap entry is 8 bytes, and entry n starts at byte offset n * 8, where n is the virtual address divided by the page size.

2. Probe one page without changing process state

The following Python program allocates one anonymous page, writes a byte so the page is actually backed, finds that page's virtual address, and reads exactly one 64-bit entry. It opens /proc/self/pagemap for the Python process itself, so there is no cross-process permission surprise in the basic example.

$ python3 - <<'PY'
import ctypes
import mmap
import os
import struct

page_size = os.sysconf("SC_PAGESIZE")
page = mmap.mmap(-1, page_size, prot=mmap.PROT_READ | mmap.PROT_WRITE)
page[0] = 0x41
address = ctypes.addressof(ctypes.c_char.from_buffer(page))
entry_offset = (address // page_size) * 8

with open("/proc/self/pagemap", "rb") as pagemap:
    raw = os.pread(pagemap.fileno(), 8, entry_offset)

if len(raw) != 8:
    raise RuntimeError(f"short pagemap read: {len(raw)} bytes")

entry, = struct.unpack("Q", raw)
print(f"virtual address: 0x{address:x}")
print(f"raw entry:      0x{entry:016x}")
print(f"present:        {(entry & (1 << 63)) != 0}")
print(f"swapped:        {(entry & (1 << 62)) != 0}")
print(f"file/shared:    {(entry & (1 << 61)) != 0}")
print(f"soft-dirty:     {(entry & (1 << 55)) != 0}")
print(f"exclusive:      {(entry & (1 << 56)) != 0}")
print(f"PFN field:      {entry & ((1 << 55) - 1)}")
PY
virtual address: 0x7f2...
raw entry:      0x8180000000000000
present:        True
swapped:        False
file/shared:    False
soft-dirty:     True
exclusive:      True
PFN field:      0

The address and raw value vary between runs. The useful shape is a successful eight-byte read with present: True. The zero PFN shown here is normal for an unprivileged process on modern kernels. Since Linux 4.2, the kernel zeroes the PFN field unless the reader has CAP_SYS_ADMIN. Do not interpret a zero PFN as proof that the page is unmapped when bit 63 says it is present.

There is no need for sudo for this self-check. Adding it may change the security context and will not make the output a stable reference value.

3. Decode the entry fields

The local manpage describes one 64-bit value per virtual page. Read the fields from the high bits first:

Bits used by the installed manpage
BitsMeaningUse in a check
63Page is present in RAMPrimary resident-page test
62Page is in swapDistinguishes swapped storage from RAM
61File-mapped or shared anonymous pageQualifies the mapping type
57Write-protected through userfaultfdRelevant only when userfaultfd is part of the design
56Page is exclusively mappedUseful, but not a universal ownership guarantee
55PTE is soft-dirtyShows the soft-dirty state of the page table entry
54-0PFN if present, or swap encoding if swappedOnly useful as a PFN when access is permitted

Bits 60-58 are zero in this manpage's description. The meanings are not all from the same kernel release: bit 61 is documented since Linux 3.5, bit 56 since 4.2, bit 57 since 5.14, and the soft-dirty bit since 3.11. Treat an unknown bit layout as a version question, not as a reason to guess.

If bit 63 is clear and bit 62 is also clear, the page is not currently present and is not represented as swapped. That usually means the virtual address is unmapped or the entry is otherwise empty. Check the address against the process's memory map before drawing a stronger conclusion.

4. Limit reads to mapped ranges

A large pagemap file is sparse from the point of view of useful work. Do not scan every possible virtual address. First read /proc/PID/maps, select the ranges relevant to your question, and seek only to the corresponding entry offsets.

$ PID=12345
$ sed -n '1,8p' "/proc/$PID/maps"
55b2c0000000-55b2c0021000 r--p 00000000 08:01 123456 /usr/bin/example
55b2c0021000-55b2c0049000 r-xp 00021000 08:01 123456 /usr/bin/example
...
$ getconf PAGESIZE
4096

For a range beginning at virtual address start, the first entry is at byte offset (start / page_size) * 8. Round or iterate at page boundaries, and stop at the range end. A map can disappear while you inspect a live process, so handle short reads and changing mappings as ordinary races. Re-read maps if a consistent snapshot matters.

Access to another process is governed by a PTRACE_MODE_READ_FSCREDS check. A readable /proc/$PID/maps does not guarantee that /proc/$PID/pagemap will be readable. Use a target process you own or are authorised to inspect. Do not work around a denial by weakening procfs or ptrace protections.

5. Interpret failures without damaging evidence

An absent /proc/PID/pagemap means the interface is unavailable for that kernel. A permission error points to the ptrace access check or file permissions. A short read can mean that the offset is beyond the available file or that the process's mapping changed. Record the PID, kernel version, page size and target address before retrying.

The PFN is sensitive information: the kernel restriction exists because physical-frame information can assist attacks such as Rowhammer. Avoid making a privileged reader a default troubleshooting step. If a controlled diagnostic genuinely requires PFNs, document why CAP_SYS_ADMIN is needed, run the smallest possible read, and remove the extra capability afterwards according to your deployment process. There is no undo operation for a PFN disclosure, so treat that decision as a security boundary.

Do not write to the file. The worked examples use only reads, and they leave the allocated Python page to be released when the process exits. If you added a long-running diagnostic, stop that diagnostic normally with Ctrl-C; it has no persistent configuration to undo.

Done means

  • /proc/self/pagemap is present and readable, and the host page size is known.
  • A self-contained probe read one eight-byte entry after touching a real page.
  • Bits 63, 62, 61, 57, 56 and 55 were interpreted using the installed manpage's version notes.
  • PFN zeroing for unprivileged readers was treated as expected modern behaviour.
  • Any multi-page investigation starts from /proc/PID/maps and handles permission failures and mapping races.
  • No procfs setting, process memory, service or persistent file was changed.