From 31fd062f25223b0b2b340abec0d63e569118a38f Mon Sep 17 00:00:00 2001 From: Omar Sandoval Date: Mon, 30 Oct 2023 12:03:13 -0700 Subject: [PATCH 1/4] pre-commit: update Black, flake8, and pre-commit-hooks No changes required. Signed-off-by: Omar Sandoval --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 20b9a1086..07738a11f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,11 +6,11 @@ repos: - id: isort name: isort (python) - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.10.1 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy @@ -20,7 +20,7 @@ repos: args: [--show-error-codes, --strict, --no-warn-return-any, --no-warn-unused-ignores] files: ^drgn/.*\.py|_drgn.pyi$ - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: trailing-whitespace exclude_types: [diff] From e4100db4d5d7e1c5fff6119475d40c2d9019b274 Mon Sep 17 00:00:00 2001 From: Omar Sandoval Date: Mon, 30 Oct 2023 10:38:32 -0700 Subject: [PATCH 2/4] drgn.helpers.linux: add maple tree helpers Maple trees have been around and used for VMAs for almost a year now (since Linux 6.1). Finally add helpers and tests for them. Closes #261. Signed-off-by: Omar Sandoval --- drgn/helpers/linux/mapletree.py | 163 +++++++ drgn/helpers/linux/xarray.py | 16 +- tests/linux_kernel/helpers/test_mapletree.py | 456 +++++++++++++++++++ tests/linux_kernel/kmod/drgn_test.c | 194 +++++++- 4 files changed, 822 insertions(+), 7 deletions(-) create mode 100644 drgn/helpers/linux/mapletree.py create mode 100644 tests/linux_kernel/helpers/test_mapletree.py diff --git a/drgn/helpers/linux/mapletree.py b/drgn/helpers/linux/mapletree.py new file mode 100644 index 000000000..e24f2f058 --- /dev/null +++ b/drgn/helpers/linux/mapletree.py @@ -0,0 +1,163 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# SPDX-License-Identifier: LGPL-2.1-or-later + +""" +Maple Trees +----------- + +The ``drgn.helpers.linux.mapletree`` module provides helpers for working with +maple trees from :linux:`include/linux/maple_tree.h`. + +Maple trees were introduced in Linux 6.1. +""" + +import collections +import operator +from typing import Iterator, Tuple + +from drgn import NULL, IntegerLike, Object, Program, sizeof +from drgn.helpers.linux.xarray import _XA_ZERO_ENTRY, _xa_is_node, xa_is_zero + +__all__ = ( + "mt_for_each", + "mtree_load", +) + + +def _ulong_max(prog: Program) -> int: + return (1 << (8 * sizeof(prog.type("unsigned long")))) - 1 + + +# Combination of mte_to_node(), mte_node_type(), ma_data_end(), and +# ma_is_leaf(). +def _mte_to_node( + prog: Program, entry_value: int, max: int +) -> Tuple[Object, Object, Object, int, bool]: + MAPLE_NODE_MASK = 255 + MAPLE_NODE_TYPE_MASK = 0xF + MAPLE_NODE_TYPE_SHIFT = 0x3 + maple_leaf_64 = 1 + maple_range_64 = 2 + maple_arange_64 = 3 + + node = Object(prog, "struct maple_node *", entry_value & ~MAPLE_NODE_MASK) + type = (entry_value >> MAPLE_NODE_TYPE_SHIFT) & MAPLE_NODE_TYPE_MASK + if type == maple_arange_64: + m = node.ma64 + pivots = m.pivot + slots = m.slot + end = m.meta.end.value_() + elif type == maple_range_64 or type == maple_leaf_64: + m = node.mr64 + pivots = m.pivot + slots = m.slot + pivot = pivots[len(pivots) - 1].value_() + if not pivot: + end = m.meta.end.value_() + elif pivot == max: + end = len(pivots) - 1 + else: + end = len(pivots) + else: + raise NotImplementedError(f"unknown maple_type {type}") + + return node, pivots, slots, end, type < maple_range_64 + + +def mtree_load(mt: Object, index: IntegerLike, *, advanced: bool = False) -> Object: + """ + Look up the entry at a given index in a maple tree. + + >>> entry = mtree_load(task.mm.mm_mt.address_of_(), 0x55d65cfaa000) + >>> cast("struct vm_area_struct *", entry) + *(struct vm_area_struct *)0xffff97ad82bfc930 = { + ... + } + + :param mt: ``struct maple_tree *`` + :param index: Entry index. + :param advanced: Whether to return nodes only visible to the maple tree + advanced API. If ``False``, zero entries (see + :func:`~drgn.helpers.linux.xarray.xa_is_zero()`) will be returned as + ``NULL``. + :return: ``void *`` found entry, or ``NULL`` if not found. + """ + prog = mt.prog_ + index = operator.index(index) + entry = mt.ma_root.read_() + entry_value = entry.value_() + if _xa_is_node(entry_value): + max = _ulong_max(prog) + while True: + node, pivots, slots, end, leaf = _mte_to_node(prog, entry_value, max) + + for offset in range(end): + pivot = pivots[offset].value_() + if pivot >= index: + max = pivot + break + else: + offset = end + + entry_value = slots[offset].value_() + if leaf: + if not advanced and entry_value == _XA_ZERO_ENTRY: + return NULL(prog, "void *") + return Object(prog, "void *", entry_value) + elif entry_value and index == 0: + return entry + else: + return NULL(prog, "void *") + + +def mt_for_each( + mt: Object, *, advanced: bool = False +) -> Iterator[Tuple[int, int, Object]]: + """ + Iterate over all of the entries and their ranges in a maple tree. + + >>> for first_index, last_index, entry in mt_for_each(task.mm.mm_mt.address_of_()): + ... print(hex(first_index), hex(last_index), entry) + ... + 0x55d65cfaa000 0x55d65cfaafff (void *)0xffff97ad82bfc930 + 0x55d65cfab000 0x55d65cfabfff (void *)0xffff97ad82bfc0a8 + 0x55d65cfac000 0x55d65cfacfff (void *)0xffff97ad82bfc000 + 0x55d65cfad000 0x55d65cfadfff (void *)0xffff97ad82bfcb28 + ... + + :param mt: ``struct maple_tree *`` + :param advanced: Whether to return nodes only visible to the maple tree + advanced API. If ``False``, zero entries (see + :func:`~drgn.helpers.linux.xarray.xa_is_zero()`) will be skipped. + :return: Iterator of (first_index, last_index, ``void *``) tuples. Both + indices are inclusive. + """ + entry = mt.ma_root.read_() + entry_value = entry.value_() + if _xa_is_node(entry_value): + prog = mt.prog_ + queue = collections.deque(((entry_value, 0, _ulong_max(prog)),)) + while queue: + entry_value, min, max = queue.popleft() + node, pivots, slots, end, leaf = _mte_to_node(prog, entry_value, max) + + if leaf: + prev = min + for offset in range(end): + pivot = pivots[offset].value_() + slot = slots[offset].read_() + if slot and (advanced or not xa_is_zero(slot)): + yield (prev, pivot, slot) + prev = pivot + 1 + slot = slots[end].read_() + if slot and (advanced or not xa_is_zero(slot)): + yield (prev, max, slot) + else: + prev = min + for offset in range(end): + pivot = pivots[offset].value_() + queue.append((slots[offset].value_(), prev, pivot)) + prev = pivot + 1 + queue.append((slots[end].value_(), prev, max)) + elif entry_value: + yield (0, 0, entry) diff --git a/drgn/helpers/linux/xarray.py b/drgn/helpers/linux/xarray.py index 77d318d1c..160031bc3 100644 --- a/drgn/helpers/linux/xarray.py +++ b/drgn/helpers/linux/xarray.py @@ -46,6 +46,10 @@ _XA_ZERO_ENTRY = 1030 # xa_mk_internal(257) +def _xa_is_node(entry_value: int) -> bool: + return (entry_value & 3) == 2 and entry_value > 4096 + + def xa_load(xa: Object, index: IntegerLike, *, advanced: bool = False) -> Object: """ Look up the entry at a given index in an XArray. @@ -142,12 +146,12 @@ def to_node(entry_value: int) -> Object: # Return > 0 if xa_is_node(), < 0 if xa_is_sibling(), and 0 otherwise. def is_internal(slots: Optional[Object], entry_value: int) -> int: - if (entry_value & 3) == 2: - if entry_value > 4096: - return 1 - elif entry_value < 256: - return -1 - return 0 + if _xa_is_node(entry_value): + return 1 + elif (entry_value & 3) == 2 and entry_value < 256: + return -1 + else: + return 0 # xa_to_node() def to_node(entry_value: int) -> Object: diff --git a/tests/linux_kernel/helpers/test_mapletree.py b/tests/linux_kernel/helpers/test_mapletree.py new file mode 100644 index 000000000..97474790c --- /dev/null +++ b/tests/linux_kernel/helpers/test_mapletree.py @@ -0,0 +1,456 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# SPDX-License-Identifier: LGPL-2.1-or-later + +import unittest + +from drgn import NULL, Object, sizeof +from drgn.helpers.linux.mapletree import mt_for_each, mtree_load +from drgn.helpers.linux.xarray import xa_is_zero +from tests.linux_kernel import LinuxKernelTestCase, skip_unless_have_test_kmod + + +@skip_unless_have_test_kmod +class TestMapleTree(LinuxKernelTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + if not cls.prog["drgn_test_have_maple_tree"]: + raise unittest.SkipTest("kernel does not have maple tree") + + def maple_trees(self, name): + yield self.prog["drgn_test_maple_tree_" + name].address_of_(), False + yield self.prog["drgn_test_maple_tree_arange_" + name].address_of_(), True + + def test_mtree_load_empty(self): + for mt, _ in self.maple_trees("empty"): + self.assertIdentical(mtree_load(mt, 0), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 666), NULL(self.prog, "void *")) + + def test_mt_for_each_empty(self): + for mt, _ in self.maple_trees("empty"): + self.assertIdentical(list(mt_for_each(mt)), []) + + def test_mtree_load_one(self): + for mt, _ in self.maple_trees("one"): + self.assertIdentical(mtree_load(mt, 0), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 665), NULL(self.prog, "void *")) + self.assertIdentical( + mtree_load(mt, 666), Object(self.prog, "void *", 0xDEADB00) + ) + self.assertIdentical(mtree_load(mt, 667), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**32 - 1), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**64 - 1), NULL(self.prog, "void *")) + + def test_mt_for_each_one(self): + for mt, _ in self.maple_trees("one"): + self.assertIdentical( + list(mt_for_each(mt)), + [(666, 666, Object(self.prog, "void *", 0xDEADB00))], + ) + + def test_mtree_load_one_range(self): + for mt, _ in self.maple_trees("one_range"): + self.assertIdentical(mtree_load(mt, 0), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 615), NULL(self.prog, "void *")) + self.assertIdentical( + mtree_load(mt, 616), Object(self.prog, "void *", 0xDEADB000) + ) + self.assertIdentical( + mtree_load(mt, 660), Object(self.prog, "void *", 0xDEADB000) + ) + self.assertIdentical( + mtree_load(mt, 666), Object(self.prog, "void *", 0xDEADB000) + ) + self.assertIdentical(mtree_load(mt, 667), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**32 - 1), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**64 - 1), NULL(self.prog, "void *")) + + def test_mt_for_each_one_range(self): + for mt, _ in self.maple_trees("one_range"): + self.assertIdentical( + list(mt_for_each(mt)), + [(616, 666, Object(self.prog, "void *", 0xDEADB000))], + ) + + def test_mtree_load_one_at_zero(self): + for mt, _ in self.maple_trees("one_at_zero"): + self.assertIdentical(mtree_load(mt, 0), Object(self.prog, "void *", 0x1234)) + self.assertIdentical(mtree_load(mt, 1), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**32 - 1), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**64 - 1), NULL(self.prog, "void *")) + + def test_mt_for_each_one_at_zero(self): + for mt, _ in self.maple_trees("one_at_zero"): + self.assertIdentical( + list(mt_for_each(mt)), [(0, 0, Object(self.prog, "void *", 0x1234))] + ) + + def test_mtree_load_one_range_at_zero(self): + for mt, _ in self.maple_trees("one_range_at_zero"): + self.assertIdentical(mtree_load(mt, 0), Object(self.prog, "void *", 0x5678)) + self.assertIdentical(mtree_load(mt, 1), Object(self.prog, "void *", 0x5678)) + self.assertIdentical( + mtree_load(mt, 0x1336), Object(self.prog, "void *", 0x5678) + ) + self.assertIdentical( + mtree_load(mt, 0x1337), Object(self.prog, "void *", 0x5678) + ) + self.assertIdentical(mtree_load(mt, 0x1338), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**32 - 1), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**64 - 1), NULL(self.prog, "void *")) + + def test_mt_for_each_one_range_at_zero(self): + for mt, _ in self.maple_trees("one_range_at_zero"): + self.assertIdentical( + list(mt_for_each(mt)), + [(0, 0x1337, Object(self.prog, "void *", 0x5678))], + ) + + def test_mtree_load_zero_entry(self): + for mt, _ in self.maple_trees("zero_entry"): + self.assertIdentical(mtree_load(mt, 666), NULL(self.prog, "void *")) + self.assertTrue(xa_is_zero(mtree_load(mt, 666, advanced=True))) + + def test_mtree_for_each_zero_entry(self): + for mt, _ in self.maple_trees("zero_entry"): + self.assertIdentical(list(mt_for_each(mt)), []) + entries = list(mt_for_each(mt, advanced=True)) + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0][:2], (666, 666)) + self.assertTrue(xa_is_zero(entries[0][2])) + + def test_mtree_load_zero_entry_at_zero(self): + for mt, _ in self.maple_trees("zero_entry_at_zero"): + self.assertIdentical(mtree_load(mt, 0), NULL(self.prog, "void *")) + self.assertTrue(xa_is_zero(mtree_load(mt, 0, advanced=True))) + + def test_mtree_for_each_zero_entry_at_zero(self): + for mt, _ in self.maple_trees("zero_entry_at_zero"): + self.assertIdentical(list(mt_for_each(mt)), []) + entries = list(mt_for_each(mt, advanced=True)) + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0][:2], (0, 0)) + self.assertTrue(xa_is_zero(entries[0][2])) + + def test_mtree_load_dense(self): + for mt, _ in self.maple_trees("dense"): + for i in range(5): + with self.subTest(i=i): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA000 | i) + ) + self.assertIdentical(mtree_load(mt, 5), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**32 - 1), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**64 - 1), NULL(self.prog, "void *")) + + def test_mt_for_each_dense(self): + for mt, _ in self.maple_trees("dense"): + self.assertIdentical( + list(mt_for_each(mt)), + [(i, i, Object(self.prog, "void *", 0xB0BA000 | i)) for i in range(5)], + ) + + def test_mtree_load_dense_ranges(self): + for mt, _ in self.maple_trees("dense_ranges"): + self.assertIdentical( + mtree_load(mt, 0), Object(self.prog, "void *", 0xB0BA000) + ) + for i in range(1, 4): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA001) + ) + for i in range(4, 9): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA002) + ) + for i in range(9, 16): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA003) + ) + for i in range(16, 25): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA004) + ) + self.assertIdentical(mtree_load(mt, 25), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**32 - 1), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**64 - 1), NULL(self.prog, "void *")) + + def test_mt_for_each_dense_ranges(self): + for mt, _ in self.maple_trees("dense_ranges"): + self.assertIdentical( + list(mt_for_each(mt)), + [ + ( + i**2, + (i + 1) ** 2 - 1, + Object(self.prog, "void *", 0xB0BA000 | i), + ) + for i in range(5) + ], + ) + + def test_mtree_load_sparse(self): + for mt, _ in self.maple_trees("sparse"): + self.assertIdentical(mtree_load(mt, 0), NULL(self.prog, "void *")) + self.assertIdentical( + mtree_load(mt, 1), Object(self.prog, "void *", 0xB0BA000) + ) + for i in range(2, 4): + self.assertIdentical(mtree_load(mt, i), NULL(self.prog, "void *")) + self.assertIdentical( + mtree_load(mt, 4), Object(self.prog, "void *", 0xB0BA001) + ) + for i in range(5, 9): + self.assertIdentical(mtree_load(mt, i), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 26), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**32 - 1), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**64 - 1), NULL(self.prog, "void *")) + + def test_mt_for_each_sparse(self): + for mt, _ in self.maple_trees("sparse"): + self.assertIdentical( + list(mt_for_each(mt)), + [ + ( + (i + 1) ** 2, + (i + 1) ** 2, + Object(self.prog, "void *", 0xB0BA000 | i), + ) + for i in range(5) + ], + ) + + def test_mtree_load_sparse_ranges(self): + for mt, _ in self.maple_trees("sparse_ranges"): + self.assertIdentical(mtree_load(mt, 0), NULL(self.prog, "void *")) + for i in range(1, 5): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA000) + ) + for i in range(5, 9): + self.assertIdentical(mtree_load(mt, i), NULL(self.prog, "void *")) + for i in range(9, 17): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA001) + ) + for i in range(17, 25): + self.assertIdentical(mtree_load(mt, i), NULL(self.prog, "void *")) + for i in range(25, 37): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA002) + ) + for i in range(37, 49): + self.assertIdentical(mtree_load(mt, i), NULL(self.prog, "void *")) + for i in range(49, 65): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA003) + ) + for i in range(65, 81): + self.assertIdentical(mtree_load(mt, i), NULL(self.prog, "void *")) + for i in range(81, 101): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA004) + ) + self.assertIdentical(mtree_load(mt, 101), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**32 - 1), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, 2**64 - 1), NULL(self.prog, "void *")) + + def test_mt_for_each_sparse_ranges(self): + for mt, _ in self.maple_trees("sparse_ranges"): + self.assertIdentical( + list(mt_for_each(mt)), + [ + ( + (2 * i + 1) ** 2, + (2 * i + 2) ** 2, + Object(self.prog, "void *", 0xB0BA000 | i), + ) + for i in range(5) + ], + ) + + def test_mtree_load_three_levels_dense_1(self): + maple_range64_slots = self.prog["drgn_test_maple_range64_slots"].value_() + ulong_max = (1 << (sizeof(self.prog.type("unsigned long")) * 8)) - 1 + for mt, arange in self.maple_trees("three_levels_dense_1"): + node_slots = self.prog[ + "drgn_test_maple_arange64_slots" + if arange + else "drgn_test_maple_range64_slots" + ].value_() + n = 2 * (node_slots - 1) * (maple_range64_slots - 1) + ( + maple_range64_slots - 1 + ) + for i in range(n): + with self.subTest(i=i): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA000 | i) + ) + self.assertIdentical(mtree_load(mt, n), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, n + 1), NULL(self.prog, "void *")) + self.assertIdentical( + mtree_load(mt, ulong_max - 1), NULL(self.prog, "void *") + ) + self.assertIdentical(mtree_load(mt, ulong_max), NULL(self.prog, "void *")) + + def test_mt_for_each_three_levels_dense_1(self): + maple_range64_slots = self.prog["drgn_test_maple_range64_slots"].value_() + for mt, arange in self.maple_trees("three_levels_dense_1"): + node_slots = self.prog[ + "drgn_test_maple_arange64_slots" + if arange + else "drgn_test_maple_range64_slots" + ].value_() + n = 2 * (node_slots - 1) * (maple_range64_slots - 1) + ( + maple_range64_slots - 1 + ) + self.assertIdentical( + list(mt_for_each(mt)), + [(i, i, Object(self.prog, "void *", 0xB0BA000 | i)) for i in range(n)], + ) + + def test_mtree_load_three_levels_dense_2(self): + maple_range64_slots = self.prog["drgn_test_maple_range64_slots"].value_() + ulong_max = (1 << (sizeof(self.prog.type("unsigned long")) * 8)) - 1 + for mt, arange in self.maple_trees("three_levels_dense_2"): + node_slots = self.prog[ + "drgn_test_maple_arange64_slots" + if arange + else "drgn_test_maple_range64_slots" + ].value_() + n = 2 * node_slots * maple_range64_slots + for i in range(n): + with self.subTest(i=i): + self.assertIdentical( + mtree_load(mt, i), Object(self.prog, "void *", 0xB0BA000 | i) + ) + self.assertIdentical(mtree_load(mt, n), NULL(self.prog, "void *")) + self.assertIdentical(mtree_load(mt, n + 1), NULL(self.prog, "void *")) + self.assertIdentical( + mtree_load(mt, ulong_max - 1), NULL(self.prog, "void *") + ) + self.assertIdentical(mtree_load(mt, ulong_max), NULL(self.prog, "void *")) + + def test_mt_for_each_three_levels_dense_2(self): + maple_range64_slots = self.prog["drgn_test_maple_range64_slots"].value_() + for mt, arange in self.maple_trees("three_levels_dense_2"): + node_slots = self.prog[ + "drgn_test_maple_arange64_slots" + if arange + else "drgn_test_maple_range64_slots" + ].value_() + n = 2 * node_slots * maple_range64_slots + self.assertIdentical( + list(mt_for_each(mt)), + [(i, i, Object(self.prog, "void *", 0xB0BA000 | i)) for i in range(n)], + ) + + def test_mtree_load_three_levels_ranges_1(self): + maple_range64_slots = self.prog["drgn_test_maple_range64_slots"].value_() + ulong_max = (1 << (sizeof(self.prog.type("unsigned long")) * 8)) - 1 + for mt, arange in self.maple_trees("three_levels_ranges_1"): + node_slots = self.prog[ + "drgn_test_maple_arange64_slots" + if arange + else "drgn_test_maple_range64_slots" + ].value_() + n = 2 * (node_slots - 1) * (maple_range64_slots - 1) + ( + maple_range64_slots - 1 + ) + for i in range(n): + with self.subTest(i=i): + self.assertIdentical( + mtree_load(mt, 2 * i), + Object(self.prog, "void *", 0xB0BA000 | i), + ) + self.assertIdentical( + mtree_load(mt, 2 * i + 1), + Object(self.prog, "void *", 0xB0BA000 | i), + ) + self.assertIdentical( + mtree_load(mt, 2 * n), Object(self.prog, "void *", 0xB0BA000 | n) + ) + self.assertIdentical( + mtree_load(mt, 2 * n + 1), Object(self.prog, "void *", 0xB0BA000 | n) + ) + self.assertIdentical( + mtree_load(mt, ulong_max - 1), + Object(self.prog, "void *", 0xB0BA000 | n), + ) + self.assertIdentical( + mtree_load(mt, ulong_max), Object(self.prog, "void *", 0xB0BA000 | n) + ) + + def test_mt_for_each_three_levels_ranges_1(self): + maple_range64_slots = self.prog["drgn_test_maple_range64_slots"].value_() + ulong_max = (1 << (sizeof(self.prog.type("unsigned long")) * 8)) - 1 + for mt, arange in self.maple_trees("three_levels_ranges_1"): + node_slots = self.prog[ + "drgn_test_maple_arange64_slots" + if arange + else "drgn_test_maple_range64_slots" + ].value_() + n = 2 * (node_slots - 1) * (maple_range64_slots - 1) + ( + maple_range64_slots - 1 + ) + self.assertIdentical( + list(mt_for_each(mt)), + [ + (2 * i, 2 * i + 1, Object(self.prog, "void *", 0xB0BA000 | i)) + for i in range(n) + ] + + [(2 * n, ulong_max, Object(self.prog, "void *", 0xB0BA000 | n))], + ) + + def test_mtree_load_three_levels_ranges_2(self): + maple_range64_slots = self.prog["drgn_test_maple_range64_slots"].value_() + ulong_max = (1 << (sizeof(self.prog.type("unsigned long")) * 8)) - 1 + for mt, arange in self.maple_trees("three_levels_ranges_2"): + node_slots = self.prog[ + "drgn_test_maple_arange64_slots" + if arange + else "drgn_test_maple_range64_slots" + ].value_() + n = 2 * node_slots * maple_range64_slots + for i in range(n): + with self.subTest(i=i): + self.assertIdentical( + mtree_load(mt, 2 * i), + Object(self.prog, "void *", 0xB0BA000 | i), + ) + self.assertIdentical( + mtree_load(mt, 2 * i + 1), + Object(self.prog, "void *", 0xB0BA000 | i), + ) + self.assertIdentical( + mtree_load(mt, 2 * n), Object(self.prog, "void *", 0xB0BA000 | n) + ) + self.assertIdentical( + mtree_load(mt, 2 * n + 1), Object(self.prog, "void *", 0xB0BA000 | n) + ) + self.assertIdentical( + mtree_load(mt, ulong_max - 1), + Object(self.prog, "void *", 0xB0BA000 | n), + ) + self.assertIdentical( + mtree_load(mt, ulong_max), Object(self.prog, "void *", 0xB0BA000 | n) + ) + + def test_mt_for_each_three_levels_ranges_2(self): + maple_range64_slots = self.prog["drgn_test_maple_range64_slots"].value_() + ulong_max = (1 << (sizeof(self.prog.type("unsigned long")) * 8)) - 1 + for mt, arange in self.maple_trees("three_levels_ranges_2"): + node_slots = self.prog[ + "drgn_test_maple_arange64_slots" + if arange + else "drgn_test_maple_range64_slots" + ].value_() + n = 2 * node_slots * maple_range64_slots + self.assertIdentical( + list(mt_for_each(mt)), + [ + (2 * i, 2 * i + 1, Object(self.prog, "void *", 0xB0BA000 | i)) + for i in range(n) + ] + + [(2 * n, ulong_max, Object(self.prog, "void *", 0xB0BA000 | n))], + ) diff --git a/tests/linux_kernel/kmod/drgn_test.c b/tests/linux_kernel/kmod/drgn_test.c index 81d9567b3..73157dd80 100644 --- a/tests/linux_kernel/kmod/drgn_test.c +++ b/tests/linux_kernel/kmod/drgn_test.c @@ -8,6 +8,8 @@ // This is intended to be used with drgn's vmtest framework, but in theory it // can be used with any kernel that has debug info enabled (at your own risk). +#include + #include #include #include @@ -15,6 +17,12 @@ #include #include #include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +#define HAVE_MAPLE_TREE 1 +#include +#else +#define HAVE_MAPLE_TREE 0 +#endif #include #include #include @@ -23,7 +31,6 @@ #include #include #include -#include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) #define HAVE_XARRAY 1 @@ -143,6 +150,187 @@ static void drgn_test_llist_init(void) llist_add(&drgn_test_singular_llist_entry.node, &drgn_test_singular_llist); } +// mapletree + +const int drgn_test_have_maple_tree = HAVE_MAPLE_TREE; +#if HAVE_MAPLE_TREE +const int drgn_test_maple_range64_slots = MAPLE_RANGE64_SLOTS; +const int drgn_test_maple_arange64_slots = MAPLE_ARANGE64_SLOTS; + +#define DRGN_TEST_MAPLE_TREES \ + X(empty) \ + X(one) \ + X(one_range) \ + X(one_at_zero) \ + X(one_range_at_zero) \ + X(zero_entry) \ + X(zero_entry_at_zero) \ + X(dense) \ + X(dense_ranges) \ + X(sparse) \ + X(sparse_ranges) \ + X(three_levels_dense_1) \ + X(three_levels_dense_2) \ + X(three_levels_ranges_1) \ + X(three_levels_ranges_2) + +#define X(name) \ + DEFINE_MTREE(drgn_test_maple_tree_##name); \ + struct maple_tree drgn_test_maple_tree_arange_##name = \ + MTREE_INIT(drgn_test_maple_tree_arange_##name, \ + MT_FLAGS_ALLOC_RANGE); +DRGN_TEST_MAPLE_TREES +#undef X + +static int drgn_test_maple_tree_init(void) +{ + int ret; + unsigned int arange, i; + #define X(name) struct maple_tree *name = &drgn_test_maple_tree_##name; + DRGN_TEST_MAPLE_TREES + #undef X + + for (arange = 0; arange < 2; arange++) { + int node_slots = arange ? MAPLE_ARANGE64_SLOTS : MAPLE_RANGE64_SLOTS; + + ret = mtree_insert(one, 666, (void *)0xdeadb00, GFP_KERNEL); + if (ret) + return ret; + + ret = mtree_insert_range(one_range, 616, 666, + (void *)0xdeadb000, GFP_KERNEL); + if (ret) + return ret; + + ret = mtree_insert(one_at_zero, 0, (void *)0x1234, GFP_KERNEL); + if (ret) + return ret; + + ret = mtree_insert_range(one_range_at_zero, 0, 0x1337, + (void *)0x5678, GFP_KERNEL); + if (ret) + return ret; + + ret = mtree_insert(zero_entry, 666, XA_ZERO_ENTRY, GFP_KERNEL); + if (ret) + return ret; + + ret = mtree_insert(zero_entry_at_zero, 0, XA_ZERO_ENTRY, + GFP_KERNEL); + if (ret) + return ret; + + for (i = 0; i < 5; i++) { + ret = mtree_insert(dense, i, + (void *)(uintptr_t)(0xb0ba000 | i), + GFP_KERNEL); + if (ret) + return ret; + } + + for (i = 0; i < 5; i++) { + ret = mtree_insert_range(dense_ranges, i * i, + (i + 1) * (i + 1) - 1, + (void *)(uintptr_t)(0xb0ba000 | i), + GFP_KERNEL); + if (ret) + return ret; + } + + for (i = 0; i < 5; i++) { + ret = mtree_insert(sparse, (i + 1) * (i + 1), + (void *)(uintptr_t)(0xb0ba000 | i), + GFP_KERNEL); + if (ret) + return ret; + } + + for (i = 0; i < 5; i++) { + ret = mtree_insert_range(sparse_ranges, + (2 * i + 1) * (2 * i + 1), + (2 * i + 2) * (2 * i + 2), + (void *)(uintptr_t)(0xb0ba000 | i), + GFP_KERNEL); + if (ret) + return ret; + } + + // In theory, a leaf can reference up to MAPLE_RANGE64_SLOTS + // entries, and a level 1 node can reference up to node_slots * + // MAPLE_RANGE64_SLOTS entries. In practice, as of Linux 6.6, + // the maple tree code only fully packs nodes with a maximum of + // ULONG_MAX. We create and test trees with both the observed + // and theoretical limits. + for (i = 0; + i < 2 * (node_slots - 1) * (MAPLE_RANGE64_SLOTS - 1) + (MAPLE_RANGE64_SLOTS - 1); + i++) { + ret = mtree_insert(three_levels_dense_1, i, + (void *)(uintptr_t)(0xb0ba000 | i), + GFP_KERNEL); + if (ret) + return ret; + } + + for (i = 0; i < 2 * node_slots * MAPLE_RANGE64_SLOTS; i++) { + ret = mtree_insert(three_levels_dense_2, i, + (void *)(uintptr_t)(0xb0ba000 | i), + GFP_KERNEL); + if (ret) + return ret; + } + + for (i = 0; + i < 2 * (node_slots - 1) * (MAPLE_RANGE64_SLOTS - 1) + (MAPLE_RANGE64_SLOTS - 1); + i++) { + ret = mtree_insert_range(three_levels_ranges_1, 2 * i, + 2 * i + 1, + (void *)(uintptr_t)(0xb0ba000 | i), + GFP_KERNEL); + if (ret) + return ret; + } + ret = mtree_insert_range(three_levels_ranges_1, 2 * i, + ULONG_MAX, + (void *)(uintptr_t)(0xb0ba000 | i), + GFP_KERNEL); + if (ret) + return ret; + + for (i = 0; i < 2 * node_slots * MAPLE_RANGE64_SLOTS; i++) { + ret = mtree_insert_range(three_levels_ranges_2, 2 * i, + 2 * i + 1, + (void *)(uintptr_t)(0xb0ba000 | i), + GFP_KERNEL); + if (ret) + return ret; + } + ret = mtree_insert_range(three_levels_ranges_2, 2 * i, + ULONG_MAX, + (void *)(uintptr_t)(0xb0ba000 | i), + GFP_KERNEL); + if (ret) + return ret; + + #define X(name) name = &drgn_test_maple_tree_arange_##name; + DRGN_TEST_MAPLE_TREES + #undef X + } + return 0; +} + +static void drgn_test_maple_tree_exit(void) +{ + #define X(name) \ + mtree_destroy(&drgn_test_maple_tree_##name); \ + mtree_destroy(&drgn_test_maple_tree_arange_##name); + DRGN_TEST_MAPLE_TREES + #undef X +} +#else +static int drgn_test_maple_tree_init(void) { return 0; } +static void drgn_test_maple_tree_exit(void) {} +#endif + // mm void *drgn_test_va; @@ -857,6 +1045,7 @@ static void drgn_test_exit(void) { drgn_test_slab_exit(); drgn_test_percpu_exit(); + drgn_test_maple_tree_exit(); drgn_test_mm_exit(); drgn_test_net_exit(); drgn_test_stack_trace_exit(); @@ -871,6 +1060,9 @@ static int __init drgn_test_init(void) drgn_test_list_init(); drgn_test_llist_init(); + ret = drgn_test_maple_tree_init(); + if (ret) + goto out; ret = drgn_test_mm_init(); if (ret) goto out; From eab64bdbc471d080e97f5f17bc586d593ebadde4 Mon Sep 17 00:00:00 2001 From: Omar Sandoval Date: Mon, 30 Oct 2023 11:24:00 -0700 Subject: [PATCH 3/4] drgn.helpers.linux.mm: add for_each_vma() helper This takes care of the transition from the VMA linked list to maple trees for users. Signed-off-by: Omar Sandoval --- drgn/helpers/linux/mm.py | 36 +++++++++++++++++++++++++ tests/linux_kernel/__init__.py | 39 +++++++++++++++++++++++++++ tests/linux_kernel/helpers/test_mm.py | 19 +++++++++++++ 3 files changed, 94 insertions(+) diff --git a/drgn/helpers/linux/mm.py b/drgn/helpers/linux/mm.py index 82a852c29..83cafc4ad 100644 --- a/drgn/helpers/linux/mm.py +++ b/drgn/helpers/linux/mm.py @@ -20,6 +20,7 @@ ) from drgn import IntegerLike, Object, Program, cast from drgn.helpers.common.format import decode_enum_type_flags +from drgn.helpers.linux.mapletree import mt_for_each __all__ = ( "PFN_PHYS", @@ -39,6 +40,7 @@ "follow_pfn", "follow_phys", "for_each_page", + "for_each_vma", "page_size", "page_to_pfn", "page_to_phys", @@ -1312,6 +1314,40 @@ def environ(task: Object) -> List[bytes]: return access_remote_vm(mm, env_start, env_end - env_start).split(b"\0")[:-1] +def for_each_vma(mm: Object) -> Iterator[Object]: + """ + Iterate over every virtual memory area (VMA) in a virtual address space. + + >>> for vma in for_each_vma(task.mm): + ... print(vma) + ... + *(struct vm_area_struct *)0xffff97ad82bfc930 = { + ... + } + *(struct vm_area_struct *)0xffff97ad82bfc0a8 = { + ... + } + ... + + :param mm: ``struct mm_struct *`` + :return: Iterator of ``struct vm_area_struct *`` objects. + """ + try: + # Since Linux kernel commit 763ecb035029 ("mm: remove the vma linked + # list") (in v6.1), VMAs are stored in a maple tree. + mt = mm.mm_mt.address_of_() + except AttributeError: + # Before that, they are in a linked list. + vma = mm.mmap + while vma: + yield vma + vma = vma.vm_next + else: + type = mm.prog_.type("struct vm_area_struct *") + for _, _, entry in mt_for_each(mt): + yield cast(type, entry) + + def totalram_pages(prog: Program) -> int: """ Return the total number of RAM memory pages. diff --git a/tests/linux_kernel/__init__.py b/tests/linux_kernel/__init__.py index 3e7dbff2e..5034e6447 100644 --- a/tests/linux_kernel/__init__.py +++ b/tests/linux_kernel/__init__.py @@ -301,6 +301,45 @@ def iter_mounts(pid="self"): _mlock.argtypes = [ctypes.c_void_p, ctypes.c_size_t] +_MAPS_RE = re.compile( + rb"(?P[0-9a-f]+)-(?P[0-9a-f]+) (?P\S+) (?P[0-9a-f]+) (?P[0-9a-f]+):(?P[0-9a-f]+) (?P[0-9]+)\s*(?P.*)" +) + + +class VmMap(NamedTuple): + start: int + end: int + read: bool + write: bool + execute: bool + shared: bool + offset: int + dev: int + ino: int + path: str + + +def iter_maps(pid="self"): + with open(f"/proc/{pid}/maps", "rb") as f: + for line in f: + match = _MAPS_RE.match(line) + flags = match["flags"] + yield VmMap( + start=int(match["start"], 16), + end=int(match["end"], 16), + read=b"r" in flags, + write=b"w" in flags, + execute=b"x" in flags, + shared=b"s" in flags, + offset=int(match["offset"], 16), + dev=os.makedev( + int(match["dev_major"], 16), int(match["dev_minor"], 16) + ), + ino=int(match["ino"]), + path=os.fsdecode(match["path"]), + ) + + def mlock(addr, len): _check_ctypes_syscall(_mlock(addr, len)) diff --git a/tests/linux_kernel/helpers/test_mm.py b/tests/linux_kernel/helpers/test_mm.py index e8e6cb411..1919699e8 100644 --- a/tests/linux_kernel/helpers/test_mm.py +++ b/tests/linux_kernel/helpers/test_mm.py @@ -30,6 +30,7 @@ follow_page, follow_pfn, follow_phys, + for_each_vma, page_size, page_to_pfn, page_to_phys, @@ -48,6 +49,8 @@ from drgn.helpers.linux.pid import find_task from tests.linux_kernel import ( LinuxKernelTestCase, + fork_and_sigwait, + iter_maps, mlock, prng32, skip_unless_have_full_mm_support, @@ -347,6 +350,22 @@ def test_environ(self): task = find_task(self.prog, os.getpid()) self.assertEqual(environ(task), proc_environ) + def test_for_each_vma(self): + with fork_and_sigwait() as pid: + self.assertEqual( + [ + (vma.vm_start, vma.vm_end) + for vma in for_each_vma(find_task(self.prog, pid).mm) + ], + [ + (map.start, map.end) + for map in iter_maps(pid) + # Gate VMAs are not included in for_each_vma(). Arm has one + # called "vectors", and x86 has one called "vsyscall". + if map.path not in ("[vectors]", "[vsyscall]") + ], + ) + def test_totalram_pages(self): with open("/proc/meminfo") as f: lines = f.read().splitlines() From cf0b75420cdb2d60d8f6fa2c9730255e973fe44b Mon Sep 17 00:00:00 2001 From: Omar Sandoval Date: Mon, 30 Oct 2023 11:25:17 -0700 Subject: [PATCH 4/4] contrib/vmmap.py: use new for_each_vma() helper Signed-off-by: Omar Sandoval --- contrib/vmmap.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/contrib/vmmap.py b/contrib/vmmap.py index ed3418747..f10f9167b 100755 --- a/contrib/vmmap.py +++ b/contrib/vmmap.py @@ -9,6 +9,7 @@ from drgn.helpers.linux.device import MAJOR, MINOR from drgn.helpers.linux.fs import d_path +from drgn.helpers.linux.mm import for_each_vma from drgn.helpers.linux.pid import find_task if len(sys.argv) != 2: @@ -19,21 +20,12 @@ if not task: sys.exit(f"Cannot find task {pid}") -try: - vma = task.mm.mmap -except AttributeError: - sys.exit('maple tree VMA mmap is not supported yet (v6.1+)') - FLAGS = ((0x1, "r"), (0x2, "w"), (0x4, "x")) PAGE_SHIFT = prog["PAGE_SHIFT"] print("Start End Flgs Offset Dev Inode File path") -# Starting with 763ecb035029f500d7e6d ("mm: remove the vma linked list") (in v6.1), -# the VMA mmap linked list is replaced with maple tree which is not supported right now: -# https://github.com/osandov/drgn/issues/261 - -while vma: +for vma in for_each_vma(task.mm): flags = "".join([v if f & vma.vm_flags else "-" for f, v in FLAGS]) flags += "s" if vma.vm_flags & 0x8 else "p" print(f"{vma.vm_start.value_():0x}-{vma.vm_end.value_():0x} {flags} ", @@ -53,5 +45,3 @@ pgoff = 0 print(f"{pgoff:08x} {major:02x}:{minor:02x} {inode:<16} {path}") - - vma = vma.vm_next