mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-22 09:13:06 +00:00
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:
parent
573bfad9fb
commit
772492838f
@ -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:
|
||||
"""
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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},
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user