From f91b0c6b8bb4c512397eb2dc308aa1e9f10e27ba Mon Sep 17 00:00:00 2001 From: Kuan-Ying Lee Date: Wed, 4 Sep 2024 14:01:47 +0800 Subject: [PATCH] contrib: add script to dump page_owner information python3 -m drgn -s ./vmlinux -c ./vmcore contrib/page_owner.py --pfn 262144 Sample output: page_owner tracks the page as allocated Page last allocated via order 0, gfp_mask: 0x140cca, pid: 74, tgid: 74 (kworker/u32:2), ts 1187644920 ns, free_ts 0 ns PFN: 262144, Flags: 0x3fffe000004003c #0 set_page_owner (./include/linux/page_owner.h:32:3) #1 post_alloc_hook (mm/page_alloc.c:1502:2) #2 prep_new_page (mm/page_alloc.c:1510:2) #3 get_page_from_freelist (mm/page_alloc.c:3489:4) #4 __alloc_pages_noprof (mm/page_alloc.c:4747:9) #5 alloc_pages_mpol_noprof (mm/mempolicy.c:2263:9) #6 folio_alloc_mpol_noprof (mm/mempolicy.c:2281:9) #7 shmem_alloc_folio (mm/shmem.c:1726:10) #8 shmem_alloc_and_add_folio (mm/shmem.c:1786:11) #9 shmem_get_folio_gfp (mm/shmem.c:2192:10) #10 shmem_get_folio (mm/shmem.c:2297:9) #11 shmem_write_begin (mm/shmem.c:2902:8) #12 generic_perform_write (mm/filemap.c:4019:12) #13 shmem_file_write_iter (mm/shmem.c:3078:8) #14 __kernel_write_iter (fs/read_write.c:523:8) #15 __kernel_write (fs/read_write.c:543:9) #16 kernel_write (fs/read_write.c:564:9) #17 kernel_write (fs/read_write.c:554:9) #18 xwrite (init/initramfs.c:33:16) #19 do_copy (init/initramfs.c:405:7) #20 write_buffer (init/initramfs.c:452:10) #21 unpack_to_rootfs (init/initramfs.c:505:14) page_owner free stack trace missing ... Signed-off-by: Kuan-Ying Lee --- contrib/page_owner.py | 281 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100755 contrib/page_owner.py diff --git a/contrib/page_owner.py b/contrib/page_owner.py new file mode 100755 index 000000000..e890ccd9e --- /dev/null +++ b/contrib/page_owner.py @@ -0,0 +1,281 @@ +#!/usr/bin/env drgn +# Copyright (c) Canonical Ltd. +# SPDX-License-Identifier: LGPL-2.1-or-later + +""" Script to dump page_owner information using drgn""" + +from argparse import ArgumentParser +import re + +from drgn import Object, cast, sizeof +from drgn.helpers.linux.kconfig import get_kconfig +from drgn.helpers.linux.mm import PFN_PHYS, PHYS_PFN, page_to_pfn, pfn_to_page +from drgn.helpers.linux.stackdepot import stack_depot_fetch + + +def DIV_ROUND_UP(n, d): + return ((n) + (d) - 1) // (d) + + +try: + MAX_ORDER_NR_PAGES = int(get_kconfig(prog)["CONFIG_ARCH_FORCE_MAX_ORDER"]) +except: + MAX_ORDER_NR_PAGES = 10 + +vmcoreinfo = prog["VMCOREINFO"].string_().decode() + + +def get_vmcoreinfo_number(item): + match = re.search(r"^NUMBER\(%s\)=([0-9]+)$" % item, vmcoreinfo, flags=re.M) + if match: + return int(match.group(1)) + else: + raise Exception("Cannot find %s in vmcoreinfo" % item) + + +SECTION_SIZE_BITS = get_vmcoreinfo_number("SECTION_SIZE_BITS") +MAX_PHYSMEM_BITS = get_vmcoreinfo_number("MAX_PHYSMEM_BITS") +SECTIONS_SHIFT = MAX_PHYSMEM_BITS - SECTION_SIZE_BITS + +NR_MEM_SECTIONS = 1 << SECTIONS_SHIFT +PFN_SECTION_SHIFT = SECTION_SIZE_BITS - prog["PAGE_SHIFT"] + +if get_kconfig(prog)["CONFIG_SPARSEMEM_EXTREME"]: + SECTIONS_PER_ROOT = prog["PAGE_SIZE"].value_() // sizeof( + prog.type("struct mem_section") + ) +else: + SECTIONS_PER_ROOT = 1 + +NR_SECTION_ROOTS = DIV_ROUND_UP(NR_MEM_SECTIONS, SECTIONS_PER_ROOT) +SECTION_ROOT_MASK = SECTIONS_PER_ROOT - 1 + +SUBSECTION_SHIFT = 21 +PFN_SUBSECTION_SHIFT = SUBSECTION_SHIFT - prog["PAGE_SHIFT"] +PAGES_PER_SUBSECTION = 1 << PFN_SUBSECTION_SHIFT + +PAGES_PER_SECTION = 1 << PFN_SECTION_SHIFT +PAGE_SECTION_MASK = ~(PAGES_PER_SECTION - 1) + +SECTION_HAS_MEM_MAP = 1 << prog["SECTION_HAS_MEM_MAP_BIT"].value_() +SECTION_IS_EARLY = 1 << prog["SECTION_IS_EARLY_BIT"].value_() + +PAGE_EXT_OWNER = prog["PAGE_EXT_OWNER"].value_() +PAGE_EXT_OWNER_ALLOCATED = prog["PAGE_EXT_OWNER_ALLOCATED"].value_() + +PAGE_EXT_INVALID = 1 + + +def pfn_to_section_nr(pfn): + return pfn >> PFN_SECTION_SHIFT + + +def section_nr_to_root(sec): + return sec.value_() // SECTIONS_PER_ROOT + + +def nr_to_section(nr): + root = section_nr_to_root(nr) + if root >= NR_SECTION_ROOTS: + raise Exception("root >= NR_SECTION_ROOTS") + return prog["mem_section"][root][nr & SECTION_ROOT_MASK] + + +def valid_section(section): + return bool( + section.address_of_() and (section.section_mem_map & SECTION_HAS_MEM_MAP) + ) + + +def early_section(section): + return bool(section.address_of_() and (section.section_mem_map & SECTION_IS_EARLY)) + + +def subsection_map_index(pfn): + return (pfn & ~(PAGE_SECTION_MASK)) // PAGES_PER_SUBSECTION + + +def pfn_section_valid(ms, pfn): + idx = subsection_map_index(pfn) + usage = ms.usage + if usage: + return (1 << idx) & usage.subsection_map + else: + return 0 + + +def pfn_to_section(pfn): + return nr_to_section(pfn_to_section_nr(pfn)) + + +def pfn_valid(pfn): + if PHYS_PFN(PFN_PHYS(pfn)) != pfn: + return 0 + if pfn_to_section_nr(pfn) >= NR_MEM_SECTIONS: + return 0 + ms = pfn_to_section(pfn) + if not valid_section(ms): + return 0 + ret = early_section(ms) or pfn_section_valid(ms, pfn) + return ret + + +def page_ext_invalid(page_ext): + if not page_ext: + return True + if page_ext.value_() & PAGE_EXT_INVALID == PAGE_EXT_INVALID: + return True + return False + + +def get_entry(base, index): + return Object( + prog, + "struct page_ext *", + (cast("unsigned long", base) + prog["page_ext_size"].value_() * index), + ) + + +def lookup_page_ext(page): + pfn = page_to_pfn(page) + section = pfn_to_section(pfn) + page_ext = section.page_ext + if page_ext_invalid(page_ext): + return drgn.NULL(prog, "unsigned long") + return get_entry(page_ext, pfn) + + +def page_ext_get(page): + page_ext = lookup_page_ext(page) + if page_ext: + return page_ext + + +def get_page_owner(page_ext): + addr = cast("unsigned long", page_ext) + prog["page_owner_ops"].offset + return Object(prog, "struct page_owner *", addr) + + +min_low_pfn = prog["min_low_pfn"] +max_pfn = prog["max_pfn"] + + +def read_page_owner(): + if prog["page_owner_inited"].key.enabled.counter != 1: + raise Exception("page_owner is not enabled") + pfn = min_low_pfn + while (not pfn_valid(pfn)) and (pfn & (MAX_ORDER_NR_PAGES - 1) != 0): + pfn += 1 + while pfn < max_pfn: + # + # If the new page is in a new MAX_ORDER_NR_PAGES area, + # validate the area as existing, skip it if not + # + if ((pfn & (MAX_ORDER_NR_PAGES - 1)) == 0) and (not pfn_valid(pfn)): + pfn += MAX_ORDER_NR_PAGES - 1 + continue + + page = pfn_to_page(pfn) + page_ext = page_ext_get(page) + if not page_ext: + pfn += 1 + continue + + if not (page_ext.flags & (1 << PAGE_EXT_OWNER)): + pfn += 1 + continue + + if not (page_ext.flags & (1 << PAGE_EXT_OWNER_ALLOCATED)): + pfn += 1 + continue + + page_owner = get_page_owner(page_ext) + trace = stack_depot_fetch(page_owner.handle) + print( + "Page allocated via order %d, gfp_mask: 0x%x, pid: %d, tgid: %d (%s), ts %u ns, free_ts %u ns" + % ( + page_owner.order, + page_owner.gfp_mask, + page_owner.pid, + page_owner.tgid, + page_owner.comm.string_().decode(), + page_owner.ts_nsec, + page_owner.free_ts_nsec, + ) + ) + print("PFN: %d, Flags: 0x%x" % (pfn, page.flags)) + print(trace) + pfn += 1 << page_owner.order + + +def read_page_owner_by_pfn(pfn): + if prog["page_owner_inited"].key.enabled.counter != 1: + raise Exception("page_owner is not enabled") + if pfn < min_low_pfn or pfn > max_pfn or (not pfn_valid(pfn)): + raise Exception("pfn is not valid") + + page = pfn_to_page(pfn) + page_ext = page_ext_get(page) + if not page_ext: + raise Exception("page_ext is not present") + + if not (page_ext.flags & (1 << PAGE_EXT_OWNER)): + print("page_owner flag is invalid") + raise Exception("page_owner info is not present (never set?)") + + if page_ext.flags & (1 << PAGE_EXT_OWNER_ALLOCATED): + print("page_owner tracks the page as allocated") + else: + print("page_owner tracks the page as freed") + + page_owner = get_page_owner(page_ext) + print( + "Page last allocated via order %d, gfp_mask: 0x%x, pid: %d, tgid: %d (%s), ts %u ns, free_ts %u ns" + % ( + page_owner.order, + page_owner.gfp_mask, + page_owner.pid, + page_owner.tgid, + page_owner.comm.string_().decode(), + page_owner.ts_nsec, + page_owner.free_ts_nsec, + ) + ) + print("PFN: %d, Flags: 0x%x" % (pfn, page.flags)) + + if page_owner.handle: + alloc_trace = stack_depot_fetch(page_owner.handle) + print(alloc_trace) + else: + print("page_owner allocation stack trace missing") + + if page_owner.free_handle: + free_trace = stack_depot_fetch(page_owner.free_handle) + print("page last free stack trace:") + print(free_trace) + else: + print("page_owner free stack trace missing") + + if page_owner.last_migrate_reason != -1: + print( + "page has been migrated, last migrate reason: %s" + % prog["migrate_reason_names"][page_owner.last_migrate_reason] + .string_() + .decode() + ) + + +if __name__ == "__main__": + parser = ArgumentParser(description="Dump page owner information") + parser.add_argument( + "--pfn", + type=int, + default=None, + help="pfn number. Default is None if not provided.", + ) + args = parser.parse_args() + if args.pfn is None: + read_page_owner() + else: + pfn = Object(prog, "unsigned long", args.pfn) + read_page_owner_by_pfn(pfn)