From c7717280ad2f867cefc62e9e841dc0787b08d39b Mon Sep 17 00:00:00 2001 From: Omar Sandoval Date: Tue, 2 Apr 2024 17:24:31 -0700 Subject: [PATCH] helpers.common.memory: add print_annotated_memory() helper This is similar to print_annotated_stack() except that it works on an arbitrary memory range. It's useful for trying to find some context in mystery memory. Signed-off-by: Omar Sandoval --- drgn/helpers/common/memory.py | 68 ++++++++++++++++++++++- drgn/helpers/common/stack.py | 3 + tests/linux_kernel/helpers/test_common.py | 16 +++++- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/drgn/helpers/common/memory.py b/drgn/helpers/common/memory.py index dad3c9dd..ab4ce0d9 100644 --- a/drgn/helpers/common/memory.py +++ b/drgn/helpers/common/memory.py @@ -9,15 +9,19 @@ The ``drgn.helpers.common.memory`` module provides helpers for working with memo """ import operator +import typing from typing import Optional import drgn -from drgn import IntegerLike, Program, SymbolKind +from drgn import IntegerLike, PlatformFlags, Program, SymbolKind from drgn.helpers.common.format import escape_ascii_string from drgn.helpers.common.prog import takes_program_or_default from drgn.helpers.linux.slab import slab_object_info -__all__ = ("identify_address",) +__all__ = ( + "identify_address", + "print_annotated_memory", +) _SYMBOL_KIND_STR = { @@ -84,3 +88,63 @@ def identify_address(prog: Program, addr: IntegerLike) -> Optional[str]: symbol_kind = _SYMBOL_KIND_STR.get(symbol.kind, "symbol") return f"{symbol_kind}: {symbol.name}+{offset}" + + +@takes_program_or_default +def print_annotated_memory( + prog: Program, address: IntegerLike, size: IntegerLike, physical: bool = False +) -> None: + """ + Print the contents of a range of memory, annotating values that can be + identified. + + Currently, this will identify any addresses in the memory range with + :func:`~drgn.helpers.common.memory.identify_address()`. + + See :func:`~drgn.helpers.common.stack.print_annotated_stack()` for a + similar function that annotates stack traces. + + >>> print_annotated_memory(0xffffffff963eb200, 56) + ADDRESS VALUE + ffffffff963eb200: 00000000000000b8 + ffffffff963eb208: 000000000000a828 + ffffffff963eb210: 0000000000000000 + ffffffff963eb218: ffff8881042948e0 [slab object: mnt_cache+0x20] + ffffffff963eb220: ffff88810074a540 [slab object: dentry+0x0] + ffffffff963eb228: ffff8881042948e0 [slab object: mnt_cache+0x20] + ffffffff963eb230: ffff88810074a540 [slab object: dentry+0x0] + + :param address: Starting address. + :param size: Number of bytes to read. + :param physical: Whether *address* is a physical memory address. If + ``False``, then it is a virtual memory address. + """ + address = operator.index(address) + mem = prog.read(address, size, physical) + + # The platform must be known if we were able to read memory. + assert prog.platform is not None + + byteorder: 'typing.Literal["little", "big"]' + if prog.platform.flags & PlatformFlags.IS_LITTLE_ENDIAN: + byteorder = "little" + else: + byteorder = "big" + + if prog.platform.flags & PlatformFlags.IS_64_BIT: + word_size = 8 + line_format = "{:016x}: {:016x}{}" + print("ADDRESS VALUE") + else: + word_size = 4 + line_format = "{:08x}: {:08x}{}" + print("ADDRESS VALUE") + + for offset in range(0, len(mem), word_size): + value = int.from_bytes(mem[offset : offset + word_size], byteorder) + identified = identify_address(prog, value) + if identified is None: + identified = "" + else: + identified = f" [{identified}]" + print(line_format.format(address + offset, value, identified)) diff --git a/drgn/helpers/common/stack.py b/drgn/helpers/common/stack.py index 63da05ba..9ff15c30 100644 --- a/drgn/helpers/common/stack.py +++ b/drgn/helpers/common/stack.py @@ -23,6 +23,9 @@ def print_annotated_stack(trace: StackTrace) -> None: Currently, this will identify any addresses on the stack with :func:`~drgn.helpers.common.memory.identify_address()`. + See :func:`~drgn.helpers.common.memory.print_annotated_memory()` for a + similar function that annotates arbitrary memory ranges. + >>> print_annotated_stack(stack_trace(1)) STACK POINTER VALUE [stack frame #0 at 0xffffffff8dc93c41 (__schedule+0x429/0x488) in context_switch at ./kernel/sched/core.c:5209:2 (inlined)] diff --git a/tests/linux_kernel/helpers/test_common.py b/tests/linux_kernel/helpers/test_common.py index 2f4fe0be..50a1dd08 100644 --- a/tests/linux_kernel/helpers/test_common.py +++ b/tests/linux_kernel/helpers/test_common.py @@ -4,7 +4,8 @@ from contextlib import redirect_stdout import io -from drgn.helpers.common.memory import identify_address +from drgn import sizeof +from drgn.helpers.common.memory import identify_address, print_annotated_memory from drgn.helpers.common.stack import print_annotated_stack from drgn.helpers.linux.mm import pfn_to_virt from tests.linux_kernel import ( @@ -63,6 +64,19 @@ class TestIdentifyAddress(LinuxKernelTestCase): self.assertIsNone(identify_address(self.prog, self.prog["drgn_test_va"])) +class TestPrintAnnotatedMemory(LinuxKernelTestCase): + @skip_unless_have_test_kmod + def test_print_annotated_memory(self): + f = io.StringIO() + with redirect_stdout(f): + print_annotated_memory( + self.prog, + self.prog["drgn_test_small_slab_objects"].address_, + sizeof(self.prog["drgn_test_small_slab_objects"]), + ) + self.assertIn("slab object: drgn_test_small+0x0", f.getvalue()) + + class TestPrintAnnotatedStack(LinuxKernelTestCase): @skip_unless_have_stack_tracing @skip_unless_have_test_kmod