drgn.helpers.linux.mm: add arbitrary address translation helpers

follow_{page,pfn,phys}() translate the virtual address by walking the
page table for a given mm_struct (built on top of the existing page
table iterator interface). vmalloc_to_page() and vmalloc_to_pfn() are
special cases for vmalloc addresses.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
This commit is contained in:
Omar Sandoval 2023-06-02 23:40:38 -07:00
parent 573bfad9fb
commit 772492838f
9 changed files with 286 additions and 1 deletions

View File

@ -2322,6 +2322,9 @@ def _linux_helper_direct_mapping_offset(prog: Program) -> int: ...
def _linux_helper_read_vm(
prog: Program, pgtable: Object, address: IntegerLike, size: IntegerLike
) -> bytes: ...
def _linux_helper_follow_phys(
prog: Program, pgtable: Object, address: IntegerLike
) -> int: ...
def _linux_helper_xa_load(xa: Object, index: IntegerLike) -> Object: ...
def _linux_helper_per_cpu_ptr(ptr: Object, cpu: IntegerLike) -> Object:
"""

View File

@ -13,7 +13,11 @@ currently supported.
import operator
from typing import Iterator, List, Optional, Union, overload
from _drgn import _linux_helper_direct_mapping_offset, _linux_helper_read_vm
from _drgn import (
_linux_helper_direct_mapping_offset,
_linux_helper_follow_phys,
_linux_helper_read_vm,
)
from drgn import IntegerLike, Object, Program, cast
from drgn.helpers.common.format import decode_enum_type_flags
@ -31,6 +35,9 @@ __all__ = (
"compound_order",
"decode_page_flags",
"environ",
"follow_page",
"follow_pfn",
"follow_phys",
"for_each_page",
"page_size",
"page_to_pfn",
@ -44,6 +51,8 @@ __all__ = (
"virt_to_page",
"virt_to_pfn",
"virt_to_phys",
"vmalloc_to_page",
"vmalloc_to_pfn",
# Generated by scripts/generate_page_flag_getters.py.
"PageActive",
"PageChecked",
@ -987,6 +996,28 @@ def virt_to_page(prog: Program, addr: IntegerLike) -> Object:
The address can be given as an :class:`~drgn.Object` or as a
:class:`~drgn.Program` and an integer.
.. _mm-helpers-direct-map:
.. note::
This only works for virtual addresses from the "direct map". This
includes address from:
* kmalloc
* Slab allocator
* Page allocator
But not:
* vmalloc
* vmap
* ioremap
* Symbols (function pointers, global variables)
For vmalloc or vmap addresses, use :func:`vmalloc_to_page(addr)
<vmalloc_to_page>`. For arbitrary kernel addresses, use
:func:`follow_page(prog["init_mm"].address_of_(), addr) <follow_page>`.
:param addr: ``void *``
:return: ``struct page *``
"""
@ -1013,6 +1044,14 @@ def virt_to_pfn(prog: Program, addr: IntegerLike) -> Object:
The address can be given as an :class:`~drgn.Object` or as a
:class:`~drgn.Program` and an integer.
.. note::
This only works for virtual addresses from the :ref:`"direct map"
<mm-helpers-direct-map>`. For vmalloc or vmap addresses, use
:func:`vmalloc_to_pfn(addr) <vmalloc_to_pfn>`. For arbitrary kernel
addresses, use :func:`follow_pfn(prog["init_mm"].address_of_(), addr)
<follow_pfn>`.
:param addr: ``void *``
:return: ``unsigned long``
"""
@ -1039,6 +1078,12 @@ def virt_to_phys(prog: Program, addr: IntegerLike) -> Object:
The address can be given as an :class:`~drgn.Object` or as a
:class:`~drgn.Program` and an integer.
.. note::
This only works for virtual addresses from the :ref:`"direct map"
<mm-helpers-direct-map>`. For arbitrary kernel addresses, use
:func:`follow_phys(prog["init_mm"].address_of_(), addr) <follow_phys>`.
:param addr: ``void *``
:return: ``phys_addr_t``
"""
@ -1062,6 +1107,125 @@ def virt_to_phys( # type: ignore # Need positional-only arguments.
)
def follow_page(mm: Object, addr: IntegerLike) -> Object:
"""
Get the page that a virtual address maps to in a virtual address space.
>>> task = find_task(prog, 113)
>>> follow_page(task.mm, 0x7fffbbb6d4d0)
*(struct page *)0xffffbe4bc0337b80 = {
...
}
:param mm: ``struct mm_struct *``
:param addr: ``void *``
:return: ``struct page *``
"""
return phys_to_page(follow_phys(mm, addr))
def follow_pfn(mm: Object, addr: IntegerLike) -> Object:
"""
Get the page frame number (PFN) that a virtual address maps to in a virtual
address space.
>>> task = find_task(prog, 113)
>>> follow_pfn(task.mm, 0x7fffbbb6d4d0)
(unsigned long)52718
:param mm: ``struct mm_struct *``
:param addr: ``void *``
:return: ``unsigned long``
"""
return PHYS_PFN(follow_phys(mm, addr))
def follow_phys(mm: Object, addr: IntegerLike) -> Object:
"""
Get the physical address that a virtual address maps to in a virtual
address space.
>>> task = find_task(prog, 113)
>>> follow_phys(task.mm, 0x7fffbbb6d4d0)
(phys_addr_t)215934160
:param mm: ``struct mm_struct *``
:param addr: ``void *``
:return: ``phys_addr_t``
"""
prog = mm.prog_
return Object(prog, "phys_addr_t", _linux_helper_follow_phys(prog, mm.pgd, addr))
@overload
def vmalloc_to_page(addr: Object) -> Object:
""""""
...
@overload
def vmalloc_to_page(prog: Program, addr: IntegerLike) -> Object:
"""
Get the page containing a vmalloc or vmap address.
The address can be given as an :class:`~drgn.Object` or as a
:class:`~drgn.Program` and an integer.
>>> task = find_task(prog, 113)
>>> vmalloc_to_page(task.stack)
*(struct page *)0xffffbe4bc00a2200 = {
...
}
:param addr: ``void *``
:return: ``struct page *``
"""
...
def vmalloc_to_page( # type: ignore # Need positional-only arguments.
prog_or_addr: Union[Program, Object], addr: Optional[IntegerLike] = None
) -> Object:
if addr is None:
assert isinstance(prog_or_addr, Object)
prog = prog_or_addr.prog_
addr = prog_or_addr
else:
assert isinstance(prog_or_addr, Program)
prog = prog_or_addr
return follow_page(prog["init_mm"].address_of_(), addr)
@overload
def vmalloc_to_pfn(addr: Object) -> Object:
""""""
...
@overload
def vmalloc_to_pfn(prog: Program, addr: IntegerLike) -> Object:
"""
Get the page frame number (PFN) containing a vmalloc or vmap address.
The address can be given as an :class:`~drgn.Object` or as a
:class:`~drgn.Program` and an integer.
>>> task = find_task(prog, 113)
>>> vmalloc_to_pfn(task.stack)
(unsigned long)10376
:param addr: ``void *``
:return: ``unsigned long``
"""
...
def vmalloc_to_pfn( # type: ignore # Need positional-only arguments.
prog_or_addr: Union[Program, Object], addr: Optional[IntegerLike] = None
) -> Object:
return page_to_pfn(vmalloc_to_page(prog_or_addr, addr)) # type: ignore
def access_process_vm(task: Object, address: IntegerLike, size: IntegerLike) -> bytes:
"""
Read memory from a task's virtual address space.

View File

@ -28,6 +28,10 @@ struct drgn_error *linux_helper_read_vm(struct drgn_program *prog,
uint64_t pgtable, uint64_t virt_addr,
void *buf, size_t count);
struct drgn_error *linux_helper_follow_phys(struct drgn_program *prog,
uint64_t pgtable,
uint64_t virt_addr, uint64_t *ret);
struct drgn_error *linux_helper_per_cpu_ptr(struct drgn_object *res,
const struct drgn_object *ptr,
uint64_t cpu);

View File

@ -190,6 +190,35 @@ out:
return err;
}
struct drgn_error *linux_helper_follow_phys(struct drgn_program *prog,
uint64_t pgtable,
uint64_t virt_addr, uint64_t *ret)
{
struct drgn_error *err;
err = begin_virtual_address_translation(prog, pgtable, virt_addr);
if (err)
return err;
struct pgtable_iterator *it = prog->pgtable_it;
pgtable_iterator_next_fn *next =
prog->platform.arch->linux_kernel_pgtable_iterator_next;
uint64_t start_virt_addr, start_phys_addr;
err = next(prog, it, &start_virt_addr, &start_phys_addr);
if (err)
goto out;
if (start_phys_addr == UINT64_MAX) {
err = drgn_error_create_fault("address is not mapped",
virt_addr);
goto out;
}
*ret = start_phys_addr + (virt_addr - start_virt_addr);
err = NULL;
out:
end_virtual_address_translation(prog);
return err;
}
struct drgn_error *linux_helper_per_cpu_ptr(struct drgn_object *res,
const struct drgn_object *ptr,
uint64_t cpu)

View File

@ -329,6 +329,8 @@ PyObject *drgnpy_linux_helper_direct_mapping_offset(PyObject *self,
PyObject *arg);
PyObject *drgnpy_linux_helper_read_vm(PyObject *self, PyObject *args,
PyObject *kwds);
PyObject *drgnpy_linux_helper_follow_phys(PyObject *self, PyObject *args,
PyObject *kwds);
DrgnObject *drgnpy_linux_helper_per_cpu_ptr(PyObject *self, PyObject *args,
PyObject *kwds);
DrgnObject *drgnpy_linux_helper_idle_task(PyObject *self, PyObject *args,

View File

@ -52,6 +52,28 @@ PyObject *drgnpy_linux_helper_read_vm(PyObject *self, PyObject *args,
return buf;
}
PyObject *drgnpy_linux_helper_follow_phys(PyObject *self, PyObject *args,
PyObject *kwds)
{
static char *keywords[] = {"prog", "pgtable", "address", NULL};
struct drgn_error *err;
Program *prog;
struct index_arg pgtable = {};
struct index_arg address = {};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O&O&:follow_phys",
keywords, &Program_type, &prog,
index_converter, &pgtable,
index_converter, &address))
return NULL;
uint64_t phys;
err = linux_helper_follow_phys(&prog->prog, pgtable.uvalue,
address.uvalue, &phys);
if (err)
return set_drgn_error(err);
return PyLong_FromUint64(phys);
}
DrgnObject *drgnpy_linux_helper_per_cpu_ptr(PyObject *self, PyObject *args,
PyObject *kwds)
{

View File

@ -125,6 +125,9 @@ static PyMethodDef drgn_methods[] = {
(PyCFunction)drgnpy_linux_helper_direct_mapping_offset, METH_O},
{"_linux_helper_read_vm", (PyCFunction)drgnpy_linux_helper_read_vm,
METH_VARARGS | METH_KEYWORDS},
{"_linux_helper_follow_phys",
(PyCFunction)drgnpy_linux_helper_follow_phys,
METH_VARARGS | METH_KEYWORDS},
{"_linux_helper_per_cpu_ptr",
(PyCFunction)drgnpy_linux_helper_per_cpu_ptr,
METH_VARARGS | METH_KEYWORDS},

View File

@ -27,6 +27,9 @@ from drgn.helpers.linux.mm import (
compound_order,
decode_page_flags,
environ,
follow_page,
follow_pfn,
follow_phys,
page_size,
page_to_pfn,
page_to_phys,
@ -39,6 +42,8 @@ from drgn.helpers.linux.mm import (
virt_to_page,
virt_to_pfn,
virt_to_phys,
vmalloc_to_page,
vmalloc_to_pfn,
)
from drgn.helpers.linux.pid import find_task
from tests.linux_kernel import (
@ -234,6 +239,49 @@ class TestMm(LinuxKernelTestCase):
self.prog.read(self.prog["drgn_test_pa"], mmap.PAGESIZE, True), expected
)
@skip_unless_have_full_mm_support
@skip_unless_have_test_kmod
def test_follow_phys(self):
self.assertEqual(
follow_phys(self.prog["init_mm"].address_of_(), self.prog["drgn_test_va"]),
self.prog["drgn_test_pa"],
)
@skip_unless_have_full_mm_support
@skip_unless_have_test_kmod
def test_follow_page(self):
self.assertEqual(
follow_page(self.prog["init_mm"].address_of_(), self.prog["drgn_test_va"]),
self.prog["drgn_test_page"],
)
@skip_unless_have_full_mm_support
@skip_unless_have_test_kmod
def test_follow_pfn(self):
self.assertEqual(
follow_pfn(self.prog["init_mm"].address_of_(), self.prog["drgn_test_va"]),
self.prog["drgn_test_pfn"],
)
task = find_task(self.prog, os.getpid())
with self._pages() as (map, address, pfns):
self.assertEqual(follow_pfn(task.mm, address), pfns[0])
@skip_unless_have_full_mm_support
@skip_unless_have_test_kmod
def test_vmalloc_to_page(self):
self.assertEqual(
vmalloc_to_page(self.prog["drgn_test_vmalloc_va"]),
self.prog["drgn_test_vmalloc_page"],
)
@skip_unless_have_full_mm_support
@skip_unless_have_test_kmod
def test_vmalloc_to_pfn(self):
self.assertEqual(
vmalloc_to_pfn(self.prog["drgn_test_vmalloc_va"]),
self.prog["drgn_test_vmalloc_pfn"],
)
@skip_unless_have_full_mm_support
def test_access_process_vm(self):
task = find_task(self.prog, os.getpid())

View File

@ -22,6 +22,7 @@
#include <linux/rbtree_augmented.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/vmalloc.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
#define HAVE_XARRAY 1
#include <linux/xarray.h>
@ -147,6 +148,9 @@ phys_addr_t drgn_test_pa;
unsigned long drgn_test_pfn;
struct page *drgn_test_page;
struct page *drgn_test_compound_page;
void *drgn_test_vmalloc_va;
unsigned long drgn_test_vmalloc_pfn;
struct page *drgn_test_vmalloc_page;
static int drgn_test_mm_init(void)
{
@ -168,11 +172,17 @@ static int drgn_test_mm_init(void)
}
drgn_test_pa = virt_to_phys(drgn_test_va);
drgn_test_pfn = PHYS_PFN(drgn_test_pa);
drgn_test_vmalloc_va = vmalloc(PAGE_SIZE);
if (!drgn_test_vmalloc_va)
return -ENOMEM;
drgn_test_vmalloc_pfn = vmalloc_to_pfn(drgn_test_vmalloc_va);
drgn_test_vmalloc_page = vmalloc_to_page(drgn_test_vmalloc_va);
return 0;
}
static void drgn_test_mm_exit(void)
{
vfree(drgn_test_vmalloc_va);
if (drgn_test_compound_page)
__free_pages(drgn_test_compound_page, 1);
if (drgn_test_page)