Commit | Line | Data |
---|---|---|
2f060190 KYL |
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 | ||
e52ec6a2 KYL |
125 | page_owner = self.get_page_owner(page_ext) |
126 | 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" %\ | |
127 | (page_owner["order"], page_owner["gfp_mask"],\ | |
128 | page_owner["pid"], page_owner["tgid"], page_owner["comm"].string(),\ | |
129 | page_owner["ts_nsec"], page_owner["free_ts_nsec"])) | |
130 | gdb.write("PFN: %d, Flags: 0x%x\n" % (pfn, page['flags'])) | |
131 | if page_owner["handle"] == 0: | |
132 | gdb.write('page_owner allocation stack trace missing\n') | |
133 | else: | |
134 | stackdepot.stack_depot_print(page_owner["handle"]) | |
2f060190 | 135 | |
e52ec6a2 KYL |
136 | if page_owner["free_handle"] == 0: |
137 | gdb.write('page_owner free stack trace missing\n') | |
138 | else: | |
139 | gdb.write('page last free stack trace:\n') | |
140 | stackdepot.stack_depot_print(page_owner["free_handle"]) | |
141 | if page_owner['last_migrate_reason'] != -1: | |
142 | gdb.write('page has been migrated, last migrate reason: %s\n' % self.migrate_reason_names[page_owner['last_migrate_reason']]) | |
2f060190 KYL |
143 | |
144 | def read_page_owner(self): | |
145 | pfn = self.min_pfn | |
146 | ||
147 | # Find a valid PFN or the start of a MAX_ORDER_NR_PAGES area | |
148 | while ((not self.p_ops.pfn_valid(pfn)) and (pfn & (self.p_ops.MAX_ORDER_NR_PAGES - 1))) != 0: | |
149 | pfn += 1 | |
150 | ||
151 | while pfn < self.max_pfn: | |
152 | # | |
153 | # If the new page is in a new MAX_ORDER_NR_PAGES area, | |
154 | # validate the area as existing, skip it if not | |
155 | # | |
156 | if ((pfn & (self.p_ops.MAX_ORDER_NR_PAGES - 1)) == 0) and (not self.p_ops.pfn_valid(pfn)): | |
157 | pfn += (self.p_ops.MAX_ORDER_NR_PAGES - 1) | |
158 | continue; | |
159 | ||
160 | page = self.p_ops.pfn_to_page(pfn) | |
161 | page_ext = self.page_ext_get(page) | |
162 | if page_ext == gdb.Value(0): | |
163 | pfn += 1 | |
164 | continue | |
165 | ||
166 | if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER)): | |
167 | pfn += 1 | |
168 | continue | |
169 | if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER_ALLOCATED)): | |
170 | pfn += 1 | |
171 | continue | |
172 | ||
e52ec6a2 KYL |
173 | page_owner = self.get_page_owner(page_ext) |
174 | gdb.write("Page allocated via order %d, gfp_mask: 0x%x, pid: %d, tgid: %d (%s), ts %u ns, free_ts %u ns\n" %\ | |
175 | (page_owner["order"], page_owner["gfp_mask"],\ | |
176 | page_owner["pid"], page_owner["tgid"], page_owner["comm"].string(),\ | |
177 | page_owner["ts_nsec"], page_owner["free_ts_nsec"])) | |
178 | gdb.write("PFN: %d, Flags: 0x%x\n" % (pfn, page['flags'])) | |
179 | stackdepot.stack_depot_print(page_owner["handle"]) | |
180 | pfn += (1 << page_owner["order"]) | |
2f060190 KYL |
181 | |
182 | DumpPageOwner() |