xref: /linux/scripts/gdb/linux/page_owner.py (revision 4b9c548d9bf16f46d75db79f192506e9929eb3ec)
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
9import gdb
10from linux import utils, stackdepot, constants, mm
11
12if 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
21def 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
30class 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    mr_never = None
38
39    def __init__(self):
40        super(DumpPageOwner, self).__init__("lx-dump-page-owner", gdb.COMMAND_SUPPORT)
41
42    def invoke(self, args, from_tty):
43        if not constants.LX_CONFIG_PAGE_OWNER:
44            raise gdb.GdbError('CONFIG_PAGE_OWNER does not enable')
45
46        page_owner_inited = gdb.parse_and_eval('page_owner_inited')
47        if page_owner_inited['key']['enabled']['counter'] != 0x1:
48            raise gdb.GdbError('page_owner_inited is not enabled')
49
50        self.p_ops = mm.page_ops().ops
51        self.get_page_owner_info()
52        argv = gdb.string_to_argv(args)
53        if len(argv) == 0:
54              self.read_page_owner()
55        elif len(argv) == 2:
56            if argv[0] == "--pfn":
57                pfn = int(argv[1])
58                self.read_page_owner_by_addr(self.p_ops.pfn_to_page(pfn))
59            else:
60                help()
61        else:
62            help()
63
64    def get_page_owner_info(self):
65        self.min_pfn = int(gdb.parse_and_eval("min_low_pfn"))
66        self.max_pfn = int(gdb.parse_and_eval("max_pfn"))
67        self.page_ext_size = int(gdb.parse_and_eval("page_ext_size"))
68        self.migrate_reason_names = gdb.parse_and_eval('migrate_reason_names')
69        self.mr_never = int(gdb.parse_and_eval('MR_NEVER'))
70
71    def page_ext_invalid(self, page_ext):
72        if page_ext == gdb.Value(0):
73            return True
74        if page_ext.cast(utils.get_ulong_type()) & PAGE_EXT_INVALID == PAGE_EXT_INVALID:
75            return True
76        return False
77
78    def get_entry(self, base, index):
79        return (base.cast(utils.get_ulong_type()) + self.page_ext_size * index).cast(page_ext_t.get_type().pointer())
80
81    def lookup_page_ext(self, page):
82        pfn = self.p_ops.page_to_pfn(page)
83        section = self.p_ops.pfn_to_section(pfn)
84        page_ext = section["page_ext"]
85        if self.page_ext_invalid(page_ext):
86            return gdb.Value(0)
87        return self.get_entry(page_ext, pfn)
88
89    def page_ext_get(self, page):
90        page_ext = self.lookup_page_ext(page)
91        if page_ext != gdb.Value(0):
92            return page_ext
93        else:
94            return gdb.Value(0)
95
96    def get_page_owner(self, page_ext):
97        addr = page_ext.cast(utils.get_ulong_type()) + gdb.parse_and_eval("page_owner_ops")["offset"].cast(utils.get_ulong_type())
98        return addr.cast(page_owner_t.get_type().pointer())
99
100    def read_page_owner_by_addr(self, struct_page_addr):
101        page = gdb.Value(struct_page_addr).cast(utils.get_page_type().pointer())
102        pfn = self.p_ops.page_to_pfn(page)
103
104        if pfn < self.min_pfn or pfn > self.max_pfn or (not self.p_ops.pfn_valid(pfn)):
105            gdb.write("pfn is invalid\n")
106            return
107
108        page = self.p_ops.pfn_to_page(pfn)
109        page_ext = self.page_ext_get(page)
110
111        if page_ext == gdb.Value(0):
112            gdb.write("page_ext is null\n")
113            return
114
115        if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER)):
116            gdb.write("page_owner flag is invalid\n")
117            raise gdb.GdbError('page_owner info is not present (never set?)\n')
118
119        if mm.test_bit(PAGE_EXT_OWNER_ALLOCATED, page_ext['flags'].address):
120            gdb.write('page_owner tracks the page as allocated\n')
121        else:
122            gdb.write('page_owner tracks the page as freed\n')
123
124        if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER_ALLOCATED)):
125            gdb.write("page_owner is not allocated\n")
126
127        page_owner = self.get_page_owner(page_ext)
128        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" %\
129                (page_owner["order"], page_owner["gfp_mask"],\
130                page_owner["pid"], page_owner["tgid"], page_owner["comm"].string(),\
131                page_owner["ts_nsec"], page_owner["free_ts_nsec"]))
132        gdb.write("PFN: %d, Flags: 0x%x\n" % (pfn, page['flags']))
133        if page_owner["handle"] == 0:
134            gdb.write('page_owner allocation stack trace missing\n')
135        else:
136            stackdepot.stack_depot_print(page_owner["handle"])
137
138        if page_owner["free_handle"] == 0:
139            gdb.write('page_owner free stack trace missing\n')
140        else:
141            gdb.write('page last free stack trace:\n')
142            stackdepot.stack_depot_print(page_owner["free_handle"])
143        if page_owner['last_migrate_reason'] != self.mr_never:
144            gdb.write('page has been migrated, last migrate reason: %s\n' % self.migrate_reason_names[page_owner['last_migrate_reason']])
145
146    def read_page_owner(self):
147        pfn = self.min_pfn
148
149        # Find a valid PFN or the start of a MAX_ORDER_NR_PAGES area
150        while ((not self.p_ops.pfn_valid(pfn)) and (pfn & (self.p_ops.MAX_ORDER_NR_PAGES - 1))) != 0:
151            pfn += 1
152
153        while pfn < self.max_pfn:
154            #
155            # If the new page is in a new MAX_ORDER_NR_PAGES area,
156            # validate the area as existing, skip it if not
157            #
158            if ((pfn & (self.p_ops.MAX_ORDER_NR_PAGES - 1)) == 0) and (not self.p_ops.pfn_valid(pfn)):
159                pfn += (self.p_ops.MAX_ORDER_NR_PAGES - 1)
160                continue;
161
162            page = self.p_ops.pfn_to_page(pfn)
163            page_ext = self.page_ext_get(page)
164            if page_ext == gdb.Value(0):
165                pfn += 1
166                continue
167
168            if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER)):
169                pfn += 1
170                continue
171            if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER_ALLOCATED)):
172                pfn += 1
173                continue
174
175            page_owner = self.get_page_owner(page_ext)
176            gdb.write("Page allocated via order %d, gfp_mask: 0x%x, pid: %d, tgid: %d (%s), ts %u ns, free_ts %u ns\n" %\
177                    (page_owner["order"], page_owner["gfp_mask"],\
178                    page_owner["pid"], page_owner["tgid"], page_owner["comm"].string(),\
179                    page_owner["ts_nsec"], page_owner["free_ts_nsec"]))
180            gdb.write("PFN: %d, Flags: 0x%x\n" % (pfn, page['flags']))
181            stackdepot.stack_depot_print(page_owner["handle"])
182            pfn += (1 << page_owner["order"])
183
184DumpPageOwner()
185