Skip to content

Commit 35bb024

Browse files
committed
tests: add mm helper tests
Add tests for pfn_to_virt(), virt_to_pfn(), pfn_to_page(), and page_to_pfn() using the pagemap interface (https://www.kernel.org/doc/html/latest/admin-guide/mm/pagemap.html). Also add tests for the PAGE_SIZE, PAGE_SHIFT, and PAGE_MASK macros.
1 parent 7a9fad0 commit 35bb024

File tree

2 files changed

+77
-2
lines changed

2 files changed

+77
-2
lines changed

tests/helpers/linux/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,17 @@ def umount(target, flags=0):
121121
raise OSError(errno, os.strerror(errno), target)
122122

123123

124+
_mlock = _c.mlock
125+
_mlock.restype = ctypes.c_int
126+
_mlock.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
127+
128+
129+
def mlock(addr, len):
130+
if _mlock(addr, len) == -1:
131+
errno = ctypes.get_errno()
132+
raise OSError(errno, os.strerror(errno))
133+
134+
124135
def create_socket(*args, **kwds):
125136
try:
126137
return socket.socket(*args, **kwds)

tests/helpers/linux/test_mm.py

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,76 @@
1+
import contextlib
2+
import ctypes
3+
import mmap
4+
import os
15
import platform
26
import re
7+
import struct
8+
import tempfile
39
import unittest
410

5-
from drgn.helpers.linux.mm import pgtable_l5_enabled
6-
from tests.helpers.linux import LinuxHelperTestCase
11+
from drgn.helpers.linux.mm import (
12+
page_to_pfn,
13+
pfn_to_page,
14+
pfn_to_virt,
15+
pgtable_l5_enabled,
16+
virt_to_pfn,
17+
)
18+
from tests.helpers.linux import LinuxHelperTestCase, mlock
719

820

921
class TestMm(LinuxHelperTestCase):
22+
def test_page_constants(self):
23+
self.assertEqual(self.prog["PAGE_SIZE"], mmap.PAGESIZE)
24+
self.assertEqual(1 << self.prog["PAGE_SHIFT"], mmap.PAGESIZE)
25+
self.assertEqual(~self.prog["PAGE_MASK"] + 1, mmap.PAGESIZE)
26+
27+
# Returns an mmap.mmap object for a file mapping and the pfns backing it.
28+
@contextlib.contextmanager
29+
def _pages(self):
30+
if not os.path.exists("/proc/self/pagemap"):
31+
self.skipTest("kernel does not support pagemap")
32+
33+
pages = 4
34+
with tempfile.TemporaryFile() as f:
35+
f.write(os.urandom(pages * mmap.PAGESIZE))
36+
f.flush()
37+
with mmap.mmap(f.fileno(), pages * mmap.PAGESIZE) as map:
38+
f.close()
39+
address = ctypes.addressof(ctypes.c_char.from_buffer(map))
40+
# Make sure the pages are faulted in and stay that way.
41+
mlock(address, pages * mmap.PAGESIZE)
42+
43+
with open("/proc/self/pagemap", "rb", buffering=0) as pagemap:
44+
pagemap.seek(address // mmap.PAGESIZE * 8)
45+
pfns = [
46+
entry & ((1 << 54) - 1)
47+
for entry in struct.unpack(f"{pages}Q", pagemap.read(pages * 8))
48+
]
49+
yield map, pfns
50+
51+
def test_virt_to_from_pfn(self):
52+
with self._pages() as (map, pfns):
53+
for i, pfn in enumerate(pfns):
54+
virt = pfn_to_virt(self.prog, pfn)
55+
# Test that we got the correct virtual address by reading from
56+
# it and comparing it to the mmap.
57+
self.assertEqual(
58+
self.prog.read(virt, mmap.PAGESIZE),
59+
map[i * mmap.PAGESIZE : (i + 1) * mmap.PAGESIZE],
60+
)
61+
# Test the opposite direction.
62+
self.assertEqual(virt_to_pfn(virt), pfn)
63+
64+
def test_pfn_to_from_page(self):
65+
with self._pages() as (map, pfns):
66+
for i, pfn in enumerate(pfns):
67+
page = pfn_to_page(self.prog, pfn)
68+
# Test that we got the correct page by looking at the index: it
69+
# should be page i in the file.
70+
self.assertEqual(page.index, i)
71+
# Test the opposite direction.
72+
self.assertEqual(page_to_pfn(page), pfn)
73+
1074
@unittest.skipUnless(platform.machine() == "x86_64", "machine is not x86_64")
1175
def test_pgtable_l5_enabled(self):
1276
with open("/proc/cpuinfo", "r") as f:

0 commit comments

Comments
 (0)