1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
#!/usr/bin/env drgn
# SPDX-License-Identifier: GPL-2.0-only
# Copyright (C) 2025 Ye Liu <liuye@kylinos.cn>
import argparse
import sys
from drgn import Object, FaultError, PlatformFlags, cast
from drgn.helpers.linux import find_task, follow_page, page_size
from drgn.helpers.linux.mm import (
decode_page_flags, page_to_pfn, page_to_phys, page_to_virt, vma_find,
PageSlab, PageCompound, PageHead, PageTail, compound_head, compound_order, compound_nr
)
from drgn.helpers.linux.cgroup import cgroup_name, cgroup_path
DESC = """
This is a drgn script to show the page state.
For more info on drgn, visit https://github.com/osandov/drgn.
"""
def format_page_data(page):
"""
Format raw page data into a readable hex dump with "RAW:" prefix.
:param page: drgn.Object instance representing the page.
:return: Formatted string of memory contents.
"""
try:
address = page.value_()
size = prog.type("struct page").size
if prog.platform.flags & PlatformFlags.IS_64_BIT:
word_size = 8
else:
word_size = 4
num_words = size // word_size
values = []
for i in range(num_words):
word_address = address + i * word_size
word = prog.read_word(word_address)
values.append(f"{word:0{word_size * 2}x}")
lines = [f"RAW: {' '.join(values[i:i + 4])}" for i in range(0, len(values), 4)]
return "\n".join(lines)
except FaultError as e:
return f"Error reading memory: {e}"
except Exception as e:
return f"Unexpected error: {e}"
def get_memcg_info(page):
"""Retrieve memory cgroup information for a page."""
try:
MEMCG_DATA_OBJEXTS = prog.constant("MEMCG_DATA_OBJEXTS").value_()
MEMCG_DATA_KMEM = prog.constant("MEMCG_DATA_KMEM").value_()
mask = prog.constant('__NR_MEMCG_DATA_FLAGS').value_() - 1
memcg_data = page.memcg_data.read_()
if memcg_data & MEMCG_DATA_OBJEXTS:
slabobj_ext = cast("struct slabobj_ext *", memcg_data & ~mask)
memcg = slabobj_ext.objcg.memcg.value_()
elif memcg_data & MEMCG_DATA_KMEM:
objcg = cast("struct obj_cgroup *", memcg_data & ~mask)
memcg = objcg.memcg.value_()
else:
memcg = cast("struct mem_cgroup *", memcg_data & ~mask)
if memcg.value_() == 0:
return "none", "/sys/fs/cgroup/memory/"
cgrp = memcg.css.cgroup
return cgroup_name(cgrp).decode(), f"/sys/fs/cgroup/memory{cgroup_path(cgrp).decode()}"
except FaultError as e:
return "unknown", f"Error retrieving memcg info: {e}"
except Exception as e:
return "unknown", f"Unexpected error: {e}"
def show_page_state(page, addr, mm, pid, task):
"""Display detailed information about a page."""
try:
print(f'PID: {pid} Comm: {task.comm.string_().decode()} mm: {hex(mm)}')
try:
print(format_page_data(page))
except FaultError as e:
print(f"Error reading page data: {e}")
fields = {
"Page Address": hex(page.value_()),
"Page Flags": decode_page_flags(page),
"Page Size": prog["PAGE_SIZE"].value_(),
"Page PFN": hex(page_to_pfn(page).value_()),
"Page Physical": hex(page_to_phys(page).value_()),
"Page Virtual": hex(page_to_virt(page).value_()),
"Page Refcount": page._refcount.counter.value_(),
"Page Mapcount": page._mapcount.counter.value_(),
"Page Index": hex(page.__folio_index.value_()),
"Page Memcg Data": hex(page.memcg_data.value_()),
}
memcg_name, memcg_path = get_memcg_info(page)
fields["Memcg Name"] = memcg_name
fields["Memcg Path"] = memcg_path
fields["Page Mapping"] = hex(page.mapping.value_())
fields["Page Anon/File"] = "Anon" if page.mapping.value_() & 0x1 else "File"
try:
vma = vma_find(mm, addr)
fields["Page VMA"] = hex(vma.value_())
fields["VMA Start"] = hex(vma.vm_start.value_())
fields["VMA End"] = hex(vma.vm_end.value_())
except FaultError as e:
fields["Page VMA"] = "Unavailable"
fields["VMA Start"] = "Unavailable"
fields["VMA End"] = "Unavailable"
print(f"Error retrieving VMA information: {e}")
# Calculate the maximum field name length for alignment
max_field_len = max(len(field) for field in fields)
# Print aligned fields
for field, value in fields.items():
print(f"{field}:".ljust(max_field_len + 2) + f"{value}")
# Additional information about the page
if PageSlab(page):
print("This page belongs to the slab allocator.")
if PageCompound(page):
print("This page is part of a compound page.")
if PageHead(page):
print("This page is the head page of a compound page.")
if PageTail(page):
print("This page is the tail page of a compound page.")
print(f"{'Head Page:'.ljust(max_field_len + 2)}{hex(compound_head(page).value_())}")
print(f"{'Compound Order:'.ljust(max_field_len + 2)}{compound_order(page).value_()}")
print(f"{'Number of Pages:'.ljust(max_field_len + 2)}{compound_nr(page).value_()}")
else:
print("This page is not part of a compound page.")
except FaultError as e:
print(f"Error accessing page state: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
def main():
"""Main function to parse arguments and display page state."""
parser = argparse.ArgumentParser(description=DESC, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('pid', metavar='PID', type=int, help='Target process ID (PID)')
parser.add_argument('vaddr', metavar='VADDR', type=str, help='Target virtual address in hexadecimal format (e.g., 0x7fff1234abcd)')
args = parser.parse_args()
try:
vaddr = int(args.vaddr, 16)
except ValueError:
sys.exit(f"Error: Invalid virtual address format: {args.vaddr}")
try:
task = find_task(args.pid)
mm = task.mm
page = follow_page(mm, vaddr)
if page:
show_page_state(page, vaddr, mm, args.pid, task)
else:
sys.exit(f"Address {hex(vaddr)} is not mapped.")
except FaultError as e:
sys.exit(f"Error accessing task or memory: {e}")
except Exception as e:
sys.exit(f"Unexpected error: {e}")
if __name__ == "__main__":
main()
|