Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

scripts/gdb/page_owner: add page owner support

This GDB script prints page owner information for user to analyze the
memory usage or memory corruption issue.

Example output from an aarch64 system:

(gdb) lx-dump-page-owner --pfn 655360
page_owner tracks the page as allocated
Page last allocated via order 0, gfp_mask: 0x8, pid: 1, tgid: 1 ("swapper/0\000\000\000\000\000\000"), ts 1295948880 ns, free_ts 1011852016 ns
PFN: 655360, Flags: 0x3fffc0000000000
0xffff8000086ab964 <post_alloc_hook+452>: ldp x19, x20, [sp, #16]
0xffff80000862e4e0 <split_map_pages+344>: cbnz w22, 0xffff80000862e57c <split_map_pages+500>
0xffff8000086370c4 <isolate_freepages_range+556>: mov x0, x27
0xffff8000086bc1cc <alloc_contig_range+808>: mov x24, x0
0xffff80000877d6d8 <cma_alloc+772>: mov w1, w0
0xffff8000082c8d18 <dma_alloc_from_contiguous+104>: ldr x19, [sp, #16]
0xffff8000082ce0e8 <atomic_pool_expand+208>: mov x19, x0
0xffff80000c1e41b4 <__dma_atomic_pool_init+172>: Cannot access memory at address 0xffff80000c1e41b4
0xffff80000c1e4298 <dma_atomic_pool_init+92>: Cannot access memory at address 0xffff80000c1e4298
0xffff8000080161d4 <do_one_initcall+176>: mov w21, w0
0xffff80000c1c1b50 <kernel_init_freeable+952>: Cannot access memory at address 0xffff80000c1c1b50
0xffff80000acf87dc <kernel_init+36>: bl 0xffff8000081ab100 <async_synchronize_full>
0xffff800008018d00 <ret_from_fork+16>: mrs x28, sp_el0
page last free stack trace:
0xffff8000086a6e8c <free_unref_page_prepare+796>: mov w2, w23
0xffff8000086aee1c <free_unref_page+96>: tst w0, #0xff
0xffff8000086af3f8 <__free_pages+292>: ldp x19, x20, [sp, #16]
0xffff80000c1f3214 <init_cma_reserved_pageblock+220>: Cannot access memory at address 0xffff80000c1f3214
0xffff80000c20363c <cma_init_reserved_areas+1284>: Cannot access memory at address 0xffff80000c20363c
0xffff8000080161d4 <do_one_initcall+176>: mov w21, w0
0xffff80000c1c1b50 <kernel_init_freeable+952>: Cannot access memory at address 0xffff80000c1c1b50
0xffff80000acf87dc <kernel_init+36>: bl 0xffff8000081ab100 <async_synchronize_full>
0xffff800008018d00 <ret_from_fork+16>: mrs x28, sp_el0

Link: https://lkml.kernel.org/r/20230808083020.22254-7-Kuan-Ying.Lee@mediatek.com
Signed-off-by: Kuan-Ying Lee <Kuan-Ying.Lee@mediatek.com>
Cc: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Cc: Chinwen Chang <chinwen.chang@mediatek.com>
Cc: Matthias Brugger <matthias.bgg@gmail.com>
Cc: Qun-Wei Lin <qun-wei.lin@mediatek.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Kuan-Ying Lee and committed by
Andrew Morton
2f060190 0e1b240a

+198
+7
scripts/gdb/linux/constants.py.in
··· 18 18 #include <linux/irq.h> 19 19 #include <linux/mount.h> 20 20 #include <linux/of_fdt.h> 21 + #include <linux/page_ext.h> 21 22 #include <linux/radix-tree.h> 22 23 #include <linux/threads.h> 23 24 ··· 90 89 LX_GDBPARSED(RADIX_TREE_MAP_SHIFT) 91 90 LX_GDBPARSED(RADIX_TREE_MAP_MASK) 92 91 92 + /* linux/page_ext.h */ 93 + if IS_BUILTIN(CONFIG_PAGE_OWNER): 94 + LX_GDBPARSED(PAGE_EXT_OWNER) 95 + LX_GDBPARSED(PAGE_EXT_OWNER_ALLOCATED) 96 + 93 97 /* Kernel Configs */ 94 98 LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS) 95 99 LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) ··· 135 129 LX_VALUE(CONFIG_NODES_SHIFT) 136 130 LX_CONFIG(CONFIG_DEBUG_VIRTUAL) 137 131 LX_CONFIG(CONFIG_STACKDEPOT) 132 + LX_CONFIG(CONFIG_PAGE_OWNER)
+190
scripts/gdb/linux/page_owner.py
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + # 3 + # Copyright (c) 2023 MediaTek Inc. 4 + # 5 + # Authors: 6 + # Kuan-Ying Lee <Kuan-Ying.Lee@mediatek.com> 7 + # 8 + 9 + import gdb 10 + from linux import utils, stackdepot, constants, mm 11 + 12 + if constants.LX_CONFIG_PAGE_OWNER: 13 + page_ext_t = utils.CachedType('struct page_ext') 14 + page_owner_t = utils.CachedType('struct page_owner') 15 + 16 + PAGE_OWNER_STACK_DEPTH = 16 17 + PAGE_EXT_OWNER = constants.LX_PAGE_EXT_OWNER 18 + PAGE_EXT_INVALID = 0x1 19 + PAGE_EXT_OWNER_ALLOCATED = constants.LX_PAGE_EXT_OWNER_ALLOCATED 20 + 21 + def help(): 22 + t = """Usage: lx-dump-page-owner [Option] 23 + Option: 24 + --pfn [Decimal pfn] 25 + Example: 26 + lx-dump-page-owner --pfn 655360\n""" 27 + gdb.write("Unrecognized command\n") 28 + raise gdb.GdbError(t) 29 + 30 + class DumpPageOwner(gdb.Command): 31 + """Dump page owner""" 32 + 33 + min_pfn = None 34 + max_pfn = None 35 + p_ops = None 36 + migrate_reason_names = None 37 + 38 + def __init__(self): 39 + super(DumpPageOwner, self).__init__("lx-dump-page-owner", gdb.COMMAND_SUPPORT) 40 + 41 + def invoke(self, args, from_tty): 42 + if not constants.LX_CONFIG_PAGE_OWNER: 43 + raise gdb.GdbError('CONFIG_PAGE_OWNER does not enable') 44 + 45 + page_owner_inited = gdb.parse_and_eval('page_owner_inited') 46 + if page_owner_inited['key']['enabled']['counter'] != 0x1: 47 + raise gdb.GdbError('page_owner_inited is not enabled') 48 + 49 + self.p_ops = mm.page_ops().ops 50 + self.get_page_owner_info() 51 + argv = gdb.string_to_argv(args) 52 + if len(argv) == 0: 53 + self.read_page_owner() 54 + elif len(argv) == 2: 55 + if argv[0] == "--pfn": 56 + pfn = int(argv[1]) 57 + self.read_page_owner_by_addr(self.p_ops.pfn_to_page(pfn)) 58 + else: 59 + help() 60 + else: 61 + help() 62 + 63 + def get_page_owner_info(self): 64 + self.min_pfn = int(gdb.parse_and_eval("min_low_pfn")) 65 + self.max_pfn = int(gdb.parse_and_eval("max_pfn")) 66 + self.page_ext_size = int(gdb.parse_and_eval("page_ext_size")) 67 + self.migrate_reason_names = gdb.parse_and_eval('migrate_reason_names') 68 + 69 + def page_ext_invalid(self, page_ext): 70 + if page_ext == gdb.Value(0): 71 + return True 72 + if page_ext.cast(utils.get_ulong_type()) & PAGE_EXT_INVALID == PAGE_EXT_INVALID: 73 + return True 74 + return False 75 + 76 + def get_entry(self, base, index): 77 + return (base.cast(utils.get_ulong_type()) + self.page_ext_size * index).cast(page_ext_t.get_type().pointer()) 78 + 79 + def lookup_page_ext(self, page): 80 + pfn = self.p_ops.page_to_pfn(page) 81 + section = self.p_ops.pfn_to_section(pfn) 82 + page_ext = section["page_ext"] 83 + if self.page_ext_invalid(page_ext): 84 + return gdb.Value(0) 85 + return self.get_entry(page_ext, pfn) 86 + 87 + def page_ext_get(self, page): 88 + page_ext = self.lookup_page_ext(page) 89 + if page_ext != gdb.Value(0): 90 + return page_ext 91 + else: 92 + return gdb.Value(0) 93 + 94 + def get_page_owner(self, page_ext): 95 + addr = page_ext.cast(utils.get_ulong_type()) + gdb.parse_and_eval("page_owner_ops")["offset"].cast(utils.get_ulong_type()) 96 + return addr.cast(page_owner_t.get_type().pointer()) 97 + 98 + def read_page_owner_by_addr(self, struct_page_addr): 99 + page = gdb.Value(struct_page_addr).cast(utils.get_page_type().pointer()) 100 + pfn = self.p_ops.page_to_pfn(page) 101 + 102 + if pfn < self.min_pfn or pfn > self.max_pfn or (not self.p_ops.pfn_valid(pfn)): 103 + gdb.write("pfn is invalid\n") 104 + return 105 + 106 + page = self.p_ops.pfn_to_page(pfn) 107 + page_ext = self.page_ext_get(page) 108 + 109 + if page_ext == gdb.Value(0): 110 + gdb.write("page_ext is null\n") 111 + return 112 + 113 + if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER)): 114 + gdb.write("page_owner flag is invalid\n") 115 + raise gdb.GdbError('page_owner info is not present (never set?)\n') 116 + 117 + if mm.test_bit(PAGE_EXT_OWNER_ALLOCATED, page_ext['flags'].address): 118 + gdb.write('page_owner tracks the page as allocated\n') 119 + else: 120 + gdb.write('page_owner tracks the page as freed\n') 121 + 122 + if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER_ALLOCATED)): 123 + gdb.write("page_owner is not allocated\n") 124 + 125 + try: 126 + page_owner = self.get_page_owner(page_ext) 127 + gdb.write("Page last allocated via order %d, gfp_mask: 0x%x, pid: %d, tgid: %d (%s), ts %u ns, free_ts %u ns\n" %\ 128 + (page_owner["order"], page_owner["gfp_mask"],\ 129 + page_owner["pid"], page_owner["tgid"], page_owner["comm"],\ 130 + page_owner["ts_nsec"], page_owner["free_ts_nsec"])) 131 + gdb.write("PFN: %d, Flags: 0x%x\n" % (pfn, page['flags'])) 132 + if page_owner["handle"] == 0: 133 + gdb.write('page_owner allocation stack trace missing\n') 134 + else: 135 + stackdepot.stack_depot_print(page_owner["handle"]) 136 + 137 + if page_owner["free_handle"] == 0: 138 + gdb.write('page_owner free stack trace missing\n') 139 + else: 140 + gdb.write('page last free stack trace:\n') 141 + stackdepot.stack_depot_print(page_owner["free_handle"]) 142 + if page_owner['last_migrate_reason'] != -1: 143 + gdb.write('page has been migrated, last migrate reason: %s\n' % self.migrate_reason_names[page_owner['last_migrate_reason']]) 144 + except: 145 + gdb.write("\n") 146 + 147 + def read_page_owner(self): 148 + pfn = self.min_pfn 149 + 150 + # Find a valid PFN or the start of a MAX_ORDER_NR_PAGES area 151 + while ((not self.p_ops.pfn_valid(pfn)) and (pfn & (self.p_ops.MAX_ORDER_NR_PAGES - 1))) != 0: 152 + pfn += 1 153 + 154 + while pfn < self.max_pfn: 155 + # 156 + # If the new page is in a new MAX_ORDER_NR_PAGES area, 157 + # validate the area as existing, skip it if not 158 + # 159 + if ((pfn & (self.p_ops.MAX_ORDER_NR_PAGES - 1)) == 0) and (not self.p_ops.pfn_valid(pfn)): 160 + pfn += (self.p_ops.MAX_ORDER_NR_PAGES - 1) 161 + continue; 162 + 163 + page = self.p_ops.pfn_to_page(pfn) 164 + page_ext = self.page_ext_get(page) 165 + if page_ext == gdb.Value(0): 166 + pfn += 1 167 + continue 168 + 169 + if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER)): 170 + pfn += 1 171 + continue 172 + if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER_ALLOCATED)): 173 + pfn += 1 174 + continue 175 + 176 + try: 177 + page_owner = self.get_page_owner(page_ext) 178 + gdb.write("Page allocated via order %d, gfp_mask: 0x%x, pid: %d, tgid: %d (%s), ts %u ns, free_ts %u ns\n" %\ 179 + (page_owner["order"], page_owner["gfp_mask"],\ 180 + page_owner["pid"], page_owner["tgid"], page_owner["comm"],\ 181 + page_owner["ts_nsec"], page_owner["free_ts_nsec"])) 182 + gdb.write("PFN: %d, Flags: 0x%x\n" % (pfn, page['flags'])) 183 + stackdepot.stack_depot_print(page_owner["handle"]) 184 + pfn += (1 << page_owner["order"]) 185 + continue 186 + except: 187 + gdb.write("\n") 188 + pfn += 1 189 + 190 + DumpPageOwner()
+1
scripts/gdb/vmlinux-gdb.py
··· 46 46 import linux.interrupts 47 47 import linux.mm 48 48 import linux.stackdepot 49 + import linux.page_owner