Skip to content

Commit 2f06019

Browse files
Kuan-Ying Leeakpm00
authored andcommitted
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>
1 parent 0e1b240 commit 2f06019

3 files changed

Lines changed: 198 additions & 0 deletions

File tree

scripts/gdb/linux/constants.py.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <linux/irq.h>
1919
#include <linux/mount.h>
2020
#include <linux/of_fdt.h>
21+
#include <linux/page_ext.h>
2122
#include <linux/radix-tree.h>
2223
#include <linux/threads.h>
2324

@@ -89,6 +90,11 @@ LX_GDBPARSED(RADIX_TREE_MAP_SIZE)
8990
LX_GDBPARSED(RADIX_TREE_MAP_SHIFT)
9091
LX_GDBPARSED(RADIX_TREE_MAP_MASK)
9192

93+
/* linux/page_ext.h */
94+
if IS_BUILTIN(CONFIG_PAGE_OWNER):
95+
LX_GDBPARSED(PAGE_EXT_OWNER)
96+
LX_GDBPARSED(PAGE_EXT_OWNER_ALLOCATED)
97+
9298
/* Kernel Configs */
9399
LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS)
94100
LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST)
@@ -129,3 +135,4 @@ if IS_BUILTIN(CONFIG_NUMA):
129135
LX_VALUE(CONFIG_NODES_SHIFT)
130136
LX_CONFIG(CONFIG_DEBUG_VIRTUAL)
131137
LX_CONFIG(CONFIG_STACKDEPOT)
138+
LX_CONFIG(CONFIG_PAGE_OWNER)

scripts/gdb/linux/page_owner.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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()

scripts/gdb/vmlinux-gdb.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@
4646
import linux.interrupts
4747
import linux.mm
4848
import linux.stackdepot
49+
import linux.page_owner

0 commit comments

Comments
 (0)