Skip to content

Instantly share code, notes, and snippets.

@osandov
Created March 24, 2020 19:22
Show Gist options
  • Save osandov/f8262082dc27c3a7b744a395be3f3671 to your computer and use it in GitHub Desktop.
Save osandov/f8262082dc27c3a7b744a395be3f3671 to your computer and use it in GitHub Desktop.
"""
Prototype implementation of x86-64 page table walking in drgn. Doesn't handle
huge pages or 5-level page tables. Probably wrong in other ways.
"""
from drgn import cast, reinterpret
from drgn.helpers.linux.mm import page_to_virt, pfn_to_page
def pgd_val(pgd):
return pgd.pgd
def pud_val(pud):
return pud.pud
def pmd_val(pmd):
return pmd.pmd
def pte_val(pte):
return pte.pte
PAGE_SHIFT = prog['PAGE_SHIFT'].value_()
PAGE_SIZE = prog['PAGE_SIZE'].value_()
PAGE_MASK = prog['PAGE_MASK'].value_()
PHYSICAL_MASK = (1 << 46) - 1
PHYSICAL_PAGE_MASK = PAGE_MASK & PHYSICAL_MASK
PAGE_PRESENT = 1 << 0
PAGE_RW = 1 << 1
PAGE_USER = 1 << 2
PAGE_ACCESSED = 1 << 5
PAGE_DIRTY = 1 << 6
PAGE_PSE = 1 << 7
PAGE_PROTNONE = 1 << 8
PAGE_ENC = 0
KERNPG_TABLE = (PAGE_PRESENT | PAGE_RW | PAGE_ACCESSED | PAGE_DIRTY |
PAGE_ENC)
PAGE_TABLE = KERNPG_TABLE | PAGE_USER
PAGE_KNL_ERRATUM_MASK = PAGE_DIRTY | PAGE_ACCESSED
try:
PAGE_OFFSET = prog['page_offset_base']
except KeyError:
major, minor = prog['UTS_RELEASE'].string_().split(b'.')[:2]
major = int(major)
minor = int(minor)
# TODO: don't detect based on kernel version, bad practice.
if (major, minor) >= (4, 20):
PAGE_OFFSET = 0xffff888000000000
else:
PAGE_OFFSET = 0xffff880000000000
# The current implementation is out of date. Monkey patch it for now.
import drgn.helpers.linux.mm
drgn.helpers.linux.mm._page_offset = lambda prog: PAGE_OFFSET
PGDIR_SHIFT = 39
PTRS_PER_PGD = 512
PGDIR_SIZE = (1 << PGDIR_SHIFT)
PGDIR_MASK = ~(PGDIR_SIZE - 1)
def native_pgd_val(pgd):
return pgd.pgd # & PGD_ALLOWED_BITS, something for paravirt I guess.
def pgd_none(pgd):
return not native_pgd_val(pgd)
def pgd_bad(pgd):
return False
PUD_SHIFT = 30
PTRS_PER_PUD = 512
PUD_SIZE = (1 << PUD_SHIFT)
PUD_MASK = ~(PUD_SIZE - 1)
PUD_PAGE_SIZE = 1 << PUD_SHIFT
PUD_PAGE_MASK = ~(PUD_PAGE_SIZE - 1)
PHYSICAL_PUD_PAGE_MASK = PUD_PAGE_MASK & PHYSICAL_MASK
def native_pud_val(pud):
return pud.pud
def pud_none(pud):
return (native_pud_val(pud) & ~PAGE_KNL_ERRATUM_MASK) == 0
def pud_bad(pud):
val = native_pud_val(pud).value_()
if val & PAGE_PSE:
flags_mask = ~PHYSICAL_PUD_PAGE_MASK
else:
flags_mask = ~PTE_PFN_MASK
flags = val & flags_mask
return (flags & ~PAGE_TABLE) != 0
def pud_huge(pud):
return (pud_val(pud) & PAGE_PSE) != 0
PMD_SHIFT = 21
PTRS_PER_PMD = 512
PMD_SIZE = (1 << PMD_SHIFT)
PMD_MASK = ~(PMD_SIZE - 1)
PMD_PAGE_SIZE = 1 << PMD_SHIFT
PMD_PAGE_MASK = ~(PMD_PAGE_SIZE - 1)
PHYSICAL_PMD_PAGE_MASK = PMD_PAGE_MASK & PHYSICAL_MASK
def native_pmd_val(pmd):
return pmd.pmd
def pmd_none(pmd):
return (native_pmd_val(pmd) & ~PAGE_KNL_ERRATUM_MASK) == 0
def pmd_flags(pmd):
val = native_pmd_val(pmd).value_()
if val & PAGE_PSE:
return val & ~PHYSICAL_PMD_PAGE_MASK
else:
return val & ~PTE_PFN_MASK
def pmd_present(pmd):
return (pmd_flags(pmd) & (PAGE_PRESENT | PAGE_PROTNONE | PAGE_PSE)) != 0
def pmd_bad(pmd):
return (pmd_flags(pmd) & ~PAGE_USER) != KERNPG_TABLE
def pmd_huge(pmd):
return (pmd_val(pmd) & (PAGE_PRESENT | PAGE_PSE)) != PAGE_PRESENT
PTRS_PER_PTE = 512
PTE_PFN_MASK = PHYSICAL_PAGE_MASK
def pte_present(pte):
return pte_val(pte) & (PAGE_PRESENT | PAGE_PROTNONE)
def va(x):
return cast('void *', cast('unsigned long', x) + PAGE_OFFSET)
def pgd_page_vaddr(pgd):
return cast('unsigned long', va(cast('unsigned long', pgd_val(pgd)) & PTE_PFN_MASK))
def pud_page_vaddr(pud):
return cast('unsigned long', va(pud_val(pud) & PTE_PFN_MASK)) # XXX
def pmd_page_vaddr(pmd):
return cast('unsigned long', va(pmd_val(pmd) & PTE_PFN_MASK))
def pgd_index(addr):
return (addr >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1)
def pud_index(addr):
return (addr >> PUD_SHIFT) & (PTRS_PER_PUD - 1)
def pmd_index(addr):
return (addr >> PMD_SHIFT) & (PTRS_PER_PMD - 1)
def pte_index(addr):
return (addr >> PAGE_SHIFT) & (PTRS_PER_PTE - 1)
def pgd_offset(mm, addr):
return mm.pgd + pgd_index(addr)
def pud_offset(pgd, addr):
return cast('pud_t *', pgd_page_vaddr(pgd[0])) + pud_index(addr)
def pmd_offset(pud, addr):
return cast('pmd_t *', pud_page_vaddr(pud[0])) + pmd_index(addr)
def pte_offset_kernel(pmd, addr):
return cast('pte_t *', pmd_page_vaddr(pmd[0])) + pte_index(addr)
def pte_pfn(pte):
return (pte_val(pte) & PTE_PFN_MASK) >> PAGE_SHIFT
def pte_page(pte):
return pfn_to_page(pte_pfn(pte))
def get_user_page(mm, addr):
"""
Get the struct page * for the given address in a given struct mm_struct *.
"""
pgd = pgd_offset(mm, addr)
p4d = pgd
pud = pud_offset(p4d, addr)
if pud_huge(pud[0]):
return pte_page(cast('pte_t *', pud)[0]) + ((addr & ~PUD_MASK) >> PAGE_SHIFT)
pmd = pmd_offset(pud, addr)
assert not pmd_huge(pmd)
pte = pte_offset_kernel(pmd, addr)[0]
return pte_page(pte)
def access_remote_vm(mm, addr, n):
"""
Read n bytes of memory starting at the given address in the given struct
mm_struct *.
"""
prog = mm.prog_
buf = bytearray()
while n > 0:
virt = page_to_virt(get_user_page(mm, addr))
page_offset = (addr & ~PAGE_MASK)
m = min(n, PAGE_SIZE - page_offset)
buf.extend(prog.read(virt.value_() + page_offset, m))
n -= m
return bytes(buf)
def cmdline(task):
"""
Get the list of command line arguments of a task (basically
`/proc/pid/cmdline`).
"""
arg_start = task.mm.arg_start.value_()
arg_end = task.mm.arg_end.value_()
return access_remote_vm(task.mm, arg_start, arg_end - arg_start).split(b'\0')[:-1]
def environ(task):
"""
Get the list of environment variables of a task (basically
`/proc/pid/environ`).
"""
env_start = task.mm.env_start.value_()
env_end = task.mm.env_end.value_()
return access_remote_vm(task.mm, env_start, env_end - env_start).split(b'\0')[:-1]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment