Skip to content

Commit

Permalink
Merge pull request #47 from delphix/projects/merge-upstream/master
Browse files Browse the repository at this point in the history
Merge remote-tracking branch '6.0/stage' into 'master'
  • Loading branch information
delphix-devops-bot authored Sep 10, 2022
2 parents e0db712 + b83f5be commit ca23e16
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 64 deletions.
114 changes: 114 additions & 0 deletions drgn/helpers/linux/mm.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@
__all__ = (
"PFN_PHYS",
"PHYS_PFN",
"PageCompound",
"PageHead",
"PageTail",
"access_process_vm",
"access_remote_vm",
"cmdline",
"compound_head",
"compound_nr",
"compound_order",
"decode_page_flags",
"environ",
"for_each_page",
"page_size",
"page_to_pfn",
"page_to_phys",
"page_to_virt",
Expand Down Expand Up @@ -558,6 +565,113 @@ def PageYoung(page: Object) -> bool:
return bool(page.flags & (1 << flag))


# End generated by scripts/generate_page_flag_getters.py.


def PageCompound(page: Object) -> bool:
"""
Return whether a page is part of a `compound page
<https://lwn.net/Articles/619514/>`_.
:param page: ``struct page *``
"""
page = page.read_()
return bool(
(page.flags & (1 << page.prog_["PG_head"])) or (page.compound_head.read_() & 1)
)


# HugeTLB Vmemmap Optimization (HVO) creates "fake" head pages that are
# actually tail pages. See Linux kernel commit e7d324850bfc ("mm: hugetlb: free
# the 2nd vmemmap page associated with each HugeTLB page") (in v5.18) and
# https://www.kernel.org/doc/html/latest/mm/vmemmap_dedup.html.
def _page_is_fake_head(page: Object) -> bool:
head = page[1].compound_head.value_()
return bool(head & 1) and (head - 1) != page.value_()


def PageHead(page: Object) -> bool:
"""
Return whether a page is a head page in a `compound page`_.
:param page: ``struct page *``
"""
page = page.read_()
return bool(
page.flags & (1 << page.prog_["PG_head"]) and not _page_is_fake_head(page)
)


def PageTail(page: Object) -> bool:
"""
Return whether a page is a tail page in a `compound page`_.
:param page: ``struct page *``
"""
page = page.read_()
if page.compound_head.value_() & 1:
return True
if page.flags & (1 << page.prog_["PG_head"]):
return _page_is_fake_head(page)
return False


def compound_head(page: Object) -> Object:
"""
Get the head page associated with a page.
If *page* is a tail page, this returns the head page of the `compound
page`_ it belongs to. Otherwise, it returns *page*.
:param page: ``struct page *``
:return: ``struct page *``
"""
page = page.read_()
head = page.compound_head.read_()
if head & 1:
return cast(page.type_, head - 1)
# Handle fake head pages (see _page_is_fake_head()).
if page.flags & (1 << page.prog_["PG_head"]):
head = page[1].compound_head.read_()
if head & 1:
return cast(page.type_, head - 1)
return page


def compound_order(page: Object) -> Object:
"""
Return the allocation order of a potentially `compound page`_.
:param page: ``struct page *``
:return: ``unsigned int``
"""
if not PageHead(page):
return Object(page.prog_, "unsigned int", 0)
return cast("unsigned int", page[1].compound_order)


def compound_nr(page: Object) -> Object:
"""
Return the number of pages in a potentially `compound page`_.
:param page: ``struct page *``
:return: ``unsigned long``
"""
if not PageHead(page):
return Object(page.prog_, "unsigned long", 1)
return Object(page.prog_, "unsigned long", 1) << page[1].compound_order


def page_size(page: Object) -> Object:
"""
Return the number of bytes in a potentially `compound page`_.
:param page: ``struct page *``
:return: ``unsigned long``
"""
return page.prog_["PAGE_SIZE"] << compound_order(page)


def decode_page_flags(page: Object) -> str:
"""
Get a human-readable representation of the flags set on a page.
Expand Down
9 changes: 8 additions & 1 deletion drgn/helpers/linux/slab.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@
from drgn.helpers import escape_ascii_string
from drgn.helpers.linux.cpumask import for_each_online_cpu
from drgn.helpers.linux.list import list_for_each_entry
from drgn.helpers.linux.mm import for_each_page, page_to_virt, pfn_to_virt, virt_to_page
from drgn.helpers.linux.mm import (
compound_head,
for_each_page,
page_to_virt,
pfn_to_virt,
virt_to_page,
)
from drgn.helpers.linux.percpu import per_cpu_ptr

__all__ = (
Expand Down Expand Up @@ -300,6 +306,7 @@ def find_containing_slab_cache( # type: ignore # Need positional-only argument
page = virt_to_page(prog, addr)

try:
page = compound_head(page)
page_flags = page.flags
except FaultError:
# Page does not exist
Expand Down
2 changes: 2 additions & 0 deletions scripts/generate_page_flag_getters.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,5 @@ def Page{uname}(page: Object) -> bool:
return bool(page.flags & (1 << flag))
'''
)
print()
print("# End generated by scripts/generate_page_flag_getters.py.")
56 changes: 56 additions & 0 deletions tests/linux_kernel/helpers/test_mm.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@
from drgn.helpers.linux.mm import (
PFN_PHYS,
PHYS_PFN,
PageCompound,
PageHead,
PageSwapBacked,
PageTail,
PageWriteback,
access_process_vm,
access_remote_vm,
cmdline,
compound_head,
compound_nr,
compound_order,
decode_page_flags,
environ,
page_size,
page_to_pfn,
page_to_phys,
page_to_virt,
Expand Down Expand Up @@ -80,6 +87,55 @@ def test_page_flag_getters(self):
self.assertTrue(PageSwapBacked(page))
self.assertFalse(PageWriteback(page))

@skip_unless_have_test_kmod
def test_PageCompound(self):
self.assertFalse(PageCompound(self.prog["drgn_test_page"]))
self.assertTrue(PageCompound(self.prog["drgn_test_compound_page"]))
self.assertTrue(PageCompound(self.prog["drgn_test_compound_page"] + 1))

@skip_unless_have_test_kmod
def test_PageHead(self):
self.assertFalse(PageHead(self.prog["drgn_test_page"]))
self.assertTrue(PageHead(self.prog["drgn_test_compound_page"]))
self.assertFalse(PageHead(self.prog["drgn_test_compound_page"] + 1))

@skip_unless_have_test_kmod
def test_PageTail(self):
self.assertFalse(PageTail(self.prog["drgn_test_page"]))
self.assertFalse(PageTail(self.prog["drgn_test_compound_page"]))
self.assertTrue(PageTail(self.prog["drgn_test_compound_page"] + 1))

@skip_unless_have_test_kmod
def test_compound_head(self):
self.assertEqual(
compound_head(self.prog["drgn_test_page"]), self.prog["drgn_test_page"]
)
self.assertEqual(
compound_head(self.prog["drgn_test_compound_page"]),
self.prog["drgn_test_compound_page"],
)
self.assertEqual(
compound_head(self.prog["drgn_test_compound_page"] + 1),
self.prog["drgn_test_compound_page"],
)

@skip_unless_have_test_kmod
def test_compound_order(self):
self.assertEqual(compound_order(self.prog["drgn_test_page"]), 0)
self.assertEqual(compound_order(self.prog["drgn_test_compound_page"]), 1)

@skip_unless_have_test_kmod
def test_compound_nr(self):
self.assertEqual(compound_nr(self.prog["drgn_test_page"]), 1)
self.assertEqual(compound_nr(self.prog["drgn_test_compound_page"]), 2)

@skip_unless_have_test_kmod
def test_page_size(self):
self.assertEqual(page_size(self.prog["drgn_test_page"]), self.prog["PAGE_SIZE"])
self.assertEqual(
page_size(self.prog["drgn_test_compound_page"]), 2 * self.prog["PAGE_SIZE"]
)

@skip_unless_have_full_mm_support
def test_decode_page_flags(self):
with self._pages() as (map, _, pfns):
Expand Down
74 changes: 34 additions & 40 deletions tests/linux_kernel/helpers/test_slab.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,50 +102,44 @@ def test_find_slab_cache(self):
@skip_unless_have_full_mm_support
@skip_unless_have_test_kmod
def test_slab_cache_for_each_allocated_object(self):
cache = self.prog["drgn_test_kmem_cache"]
objects = self.prog["drgn_test_slab_objects"]
if self.prog["drgn_test_slob"]:
self.assertRaisesRegex(
ValueError,
"SLOB is not supported",
next,
slab_cache_for_each_allocated_object(
cache, "struct drgn_test_slab_object"
),
)
else:
self.assertEqual(
sorted(
slab_cache_for_each_allocated_object(
cache, "struct drgn_test_slab_object"
),
key=lambda obj: obj.value.value_(),
),
[objects[i] for i in range(5)],
)
for size in ("small", "big"):
with self.subTest(size=size):
cache = self.prog[f"drgn_test_{size}_kmem_cache"]
objects = self.prog[f"drgn_test_{size}_slab_objects"]
if self.prog["drgn_test_slob"]:
self.assertRaisesRegex(
ValueError,
"SLOB is not supported",
next,
slab_cache_for_each_allocated_object(
cache, f"struct drgn_test_{size}_slab_object"
),
)
else:
self.assertEqual(
sorted(
slab_cache_for_each_allocated_object(
cache, f"struct drgn_test_{size}_slab_object"
),
key=lambda obj: obj.value.value_(),
),
list(objects),
)

@skip_unless_have_full_mm_support
@skip_unless_have_test_kmod
def test_find_containing_slab_cache(self):
cache = self.prog["drgn_test_kmem_cache"]
objects = self.prog["drgn_test_slab_objects"]

if self.prog["drgn_test_slob"]:
for obj in objects:
self.assertEqual(
find_containing_slab_cache(self.prog, obj.value_()),
NULL(self.prog, "struct kmem_cache *"),
)
self.assertEqual(
find_containing_slab_cache(obj),
NULL(self.prog, "struct kmem_cache *"),
)
else:
for obj in objects:
self.assertEqual(
find_containing_slab_cache(self.prog, obj.value_()), cache
)
self.assertEqual(find_containing_slab_cache(obj), cache)
for size in ("small", "big"):
with self.subTest(size=size):
cache = self.prog[f"drgn_test_{size}_kmem_cache"]
if self.prog["drgn_test_slob"]:
cache = NULL(self.prog, "struct kmem_cache *")
objects = self.prog[f"drgn_test_{size}_slab_objects"]
for obj in objects:
self.assertEqual(
find_containing_slab_cache(self.prog, obj.value_()), cache
)
self.assertEqual(find_containing_slab_cache(obj), cache)

@skip_unless_have_full_mm_support
@skip_unless_have_test_kmod
Expand Down
Loading

0 comments on commit ca23e16

Please sign in to comment.