Created
March 24, 2020 19:22
-
-
Save osandov/f8262082dc27c3a7b744a395be3f3671 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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