Commit | Line | Data |
---|---|---|
79939c4a 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 | import re | |
11 | import traceback | |
12 | from linux import lists, utils, stackdepot, constants, mm | |
13 | ||
14 | SLAB_RED_ZONE = constants.LX_SLAB_RED_ZONE | |
15 | SLAB_POISON = constants.LX_SLAB_POISON | |
16 | SLAB_KMALLOC = constants.LX_SLAB_KMALLOC | |
17 | SLAB_HWCACHE_ALIGN = constants.LX_SLAB_HWCACHE_ALIGN | |
18 | SLAB_CACHE_DMA = constants.LX_SLAB_CACHE_DMA | |
19 | SLAB_CACHE_DMA32 = constants.LX_SLAB_CACHE_DMA32 | |
20 | SLAB_STORE_USER = constants.LX_SLAB_STORE_USER | |
21 | SLAB_PANIC = constants.LX_SLAB_PANIC | |
22 | ||
23 | OO_SHIFT = 16 | |
24 | OO_MASK = (1 << OO_SHIFT) - 1 | |
25 | ||
26 | if constants.LX_CONFIG_SLUB_DEBUG: | |
27 | slab_type = utils.CachedType("struct slab") | |
28 | slab_ptr_type = slab_type.get_type().pointer() | |
29 | kmem_cache_type = utils.CachedType("struct kmem_cache") | |
30 | kmem_cache_ptr_type = kmem_cache_type.get_type().pointer() | |
31 | freeptr_t = utils.CachedType("freeptr_t") | |
32 | freeptr_t_ptr = freeptr_t.get_type().pointer() | |
33 | ||
34 | track_type = gdb.lookup_type('struct track') | |
35 | track_alloc = int(gdb.parse_and_eval('TRACK_ALLOC')) | |
36 | track_free = int(gdb.parse_and_eval('TRACK_FREE')) | |
37 | ||
38 | def slab_folio(slab): | |
39 | return slab.cast(gdb.lookup_type("struct folio").pointer()) | |
40 | ||
41 | def slab_address(slab): | |
42 | p_ops = mm.page_ops().ops | |
43 | folio = slab_folio(slab) | |
44 | return p_ops.folio_address(folio) | |
45 | ||
46 | def for_each_object(cache, addr, slab_objects): | |
47 | p = addr | |
48 | if cache['flags'] & SLAB_RED_ZONE: | |
49 | p += int(cache['red_left_pad']) | |
50 | while p < addr + (slab_objects * cache['size']): | |
51 | yield p | |
52 | p = p + int(cache['size']) | |
53 | ||
54 | def get_info_end(cache): | |
55 | if (cache['offset'] >= cache['inuse']): | |
56 | return cache['inuse'] + gdb.lookup_type("void").pointer().sizeof | |
57 | else: | |
58 | return cache['inuse'] | |
59 | ||
60 | def get_orig_size(cache, obj): | |
61 | if cache['flags'] & SLAB_STORE_USER and cache['flags'] & SLAB_KMALLOC: | |
62 | p = mm.page_ops().ops.kasan_reset_tag(obj) | |
63 | p += get_info_end(cache) | |
64 | p += gdb.lookup_type('struct track').sizeof * 2 | |
65 | p = p.cast(utils.get_uint_type().pointer()) | |
66 | return p.dereference() | |
67 | else: | |
68 | return cache['object_size'] | |
69 | ||
70 | def get_track(cache, object_pointer, alloc): | |
71 | p = object_pointer + get_info_end(cache) | |
72 | p += (alloc * track_type.sizeof) | |
73 | return p | |
74 | ||
75 | def oo_objects(x): | |
76 | return int(x['x']) & OO_MASK | |
77 | ||
78 | def oo_order(x): | |
79 | return int(x['x']) >> OO_SHIFT | |
80 | ||
81 | def reciprocal_divide(a, R): | |
82 | t = (a * int(R['m'])) >> 32 | |
83 | return (t + ((a - t) >> int(R['sh1']))) >> int(R['sh2']) | |
84 | ||
85 | def __obj_to_index(cache, addr, obj): | |
86 | return reciprocal_divide(int(mm.page_ops().ops.kasan_reset_tag(obj)) - addr, cache['reciprocal_size']) | |
87 | ||
88 | def swab64(x): | |
89 | result = (((x & 0x00000000000000ff) << 56) | \ | |
90 | ((x & 0x000000000000ff00) << 40) | \ | |
91 | ((x & 0x0000000000ff0000) << 24) | \ | |
92 | ((x & 0x00000000ff000000) << 8) | \ | |
93 | ((x & 0x000000ff00000000) >> 8) | \ | |
94 | ((x & 0x0000ff0000000000) >> 24) | \ | |
95 | ((x & 0x00ff000000000000) >> 40) | \ | |
96 | ((x & 0xff00000000000000) >> 56)) | |
97 | return result | |
98 | ||
99 | def freelist_ptr_decode(cache, ptr, ptr_addr): | |
100 | if constants.LX_CONFIG_SLAB_FREELIST_HARDENED: | |
101 | return ptr['v'] ^ cache['random'] ^ swab64(int(ptr_addr)) | |
102 | else: | |
103 | return ptr['v'] | |
104 | ||
105 | def get_freepointer(cache, obj): | |
106 | obj = mm.page_ops().ops.kasan_reset_tag(obj) | |
107 | ptr_addr = obj + cache['offset'] | |
108 | p = ptr_addr.cast(freeptr_t_ptr).dereference() | |
109 | return freelist_ptr_decode(cache, p, ptr_addr) | |
110 | ||
111 | def loc_exist(loc_track, addr, handle, waste): | |
112 | for loc in loc_track: | |
113 | if loc['addr'] == addr and loc['handle'] == handle and loc['waste'] == waste: | |
114 | return loc | |
115 | return None | |
116 | ||
117 | def add_location(loc_track, cache, track, orig_size): | |
118 | jiffies = gdb.parse_and_eval("jiffies_64") | |
119 | age = jiffies - track['when'] | |
120 | handle = 0 | |
121 | waste = cache['object_size'] - int(orig_size) | |
122 | pid = int(track['pid']) | |
123 | cpuid = int(track['cpu']) | |
124 | addr = track['addr'] | |
125 | if constants.LX_CONFIG_STACKDEPOT: | |
126 | handle = track['handle'] | |
127 | ||
128 | loc = loc_exist(loc_track, addr, handle, waste) | |
129 | if loc: | |
130 | loc['count'] += 1 | |
131 | if track['when']: | |
132 | loc['sum_time'] += age | |
133 | loc['min_time'] = min(loc['min_time'], age) | |
134 | loc['max_time'] = max(loc['max_time'], age) | |
135 | loc['min_pid'] = min(loc['min_pid'], pid) | |
136 | loc['max_pid'] = max(loc['max_pid'], pid) | |
137 | loc['cpus'].add(cpuid) | |
138 | else: | |
139 | loc_track.append({ | |
140 | 'count' : 1, | |
141 | 'addr' : addr, | |
142 | 'sum_time' : age, | |
143 | 'min_time' : age, | |
144 | 'max_time' : age, | |
145 | 'min_pid' : pid, | |
146 | 'max_pid' : pid, | |
147 | 'handle' : handle, | |
148 | 'waste' : waste, | |
149 | 'cpus' : {cpuid} | |
150 | } | |
151 | ) | |
152 | ||
153 | def slabtrace(alloc, cache_name): | |
154 | ||
155 | def __fill_map(obj_map, cache, slab): | |
156 | p = slab['freelist'] | |
157 | addr = slab_address(slab) | |
158 | while p != gdb.Value(0): | |
159 | index = __obj_to_index(cache, addr, p) | |
160 | obj_map[index] = True # free objects | |
161 | p = get_freepointer(cache, p) | |
162 | ||
163 | # process every slab page on the slab_list (partial and full list) | |
164 | def process_slab(loc_track, slab_list, alloc, cache): | |
165 | for slab in lists.list_for_each_entry(slab_list, slab_ptr_type, "slab_list"): | |
166 | obj_map[:] = [False] * oo_objects(cache['oo']) | |
167 | __fill_map(obj_map, cache, slab) | |
168 | addr = slab_address(slab) | |
169 | for object_pointer in for_each_object(cache, addr, slab['objects']): | |
170 | if obj_map[__obj_to_index(cache, addr, object_pointer)] == True: | |
171 | continue | |
172 | p = get_track(cache, object_pointer, alloc) | |
173 | track = gdb.Value(p).cast(track_type.pointer()) | |
174 | if alloc == track_alloc: | |
175 | size = get_orig_size(cache, object_pointer) | |
176 | else: | |
177 | size = cache['object_size'] | |
178 | add_location(loc_track, cache, track, size) | |
179 | continue | |
180 | ||
181 | slab_caches = gdb.parse_and_eval("slab_caches") | |
182 | if mm.page_ops().ops.MAX_NUMNODES > 1: | |
183 | nr_node_ids = int(gdb.parse_and_eval("nr_node_ids")) | |
184 | else: | |
185 | nr_node_ids = 1 | |
186 | ||
187 | target_cache = None | |
188 | loc_track = [] | |
189 | ||
190 | for cache in lists.list_for_each_entry(slab_caches, kmem_cache_ptr_type, 'list'): | |
191 | if cache['name'].string() == cache_name: | |
192 | target_cache = cache | |
193 | break | |
194 | ||
195 | obj_map = [False] * oo_objects(target_cache['oo']) | |
196 | ||
197 | if target_cache['flags'] & SLAB_STORE_USER: | |
198 | for i in range(0, nr_node_ids): | |
199 | cache_node = target_cache['node'][i] | |
200 | if cache_node['nr_slabs']['counter'] == 0: | |
201 | continue | |
202 | process_slab(loc_track, cache_node['partial'], alloc, target_cache) | |
203 | process_slab(loc_track, cache_node['full'], alloc, target_cache) | |
204 | else: | |
205 | raise gdb.GdbError("SLAB_STORE_USER is not set in %s" % target_cache['name'].string()) | |
206 | ||
207 | for loc in sorted(loc_track, key=lambda x:x['count'], reverse=True): | |
208 | if loc['addr']: | |
209 | addr = loc['addr'].cast(utils.get_ulong_type().pointer()) | |
210 | gdb.write("%d %s" % (loc['count'], str(addr).split(' ')[-1])) | |
211 | else: | |
212 | gdb.write("%d <not-available>" % loc['count']) | |
213 | ||
214 | if loc['waste']: | |
215 | gdb.write(" waste=%d/%d" % (loc['count'] * loc['waste'], loc['waste'])) | |
216 | ||
217 | if loc['sum_time'] != loc['min_time']: | |
218 | gdb.write(" age=%d/%d/%d" % (loc['min_time'], loc['sum_time']/loc['count'], loc['max_time'])) | |
219 | else: | |
220 | gdb.write(" age=%d" % loc['min_time']) | |
221 | ||
222 | if loc['min_pid'] != loc['max_pid']: | |
223 | gdb.write(" pid=%d-%d" % (loc['min_pid'], loc['max_pid'])) | |
224 | else: | |
225 | gdb.write(" pid=%d" % loc['min_pid']) | |
226 | ||
227 | if constants.LX_NR_CPUS > 1: | |
228 | nr_cpu = gdb.parse_and_eval('__num_online_cpus')['counter'] | |
229 | if nr_cpu > 1: | |
230 | gdb.write(" cpus=") | |
e52ec6a2 | 231 | gdb.write(','.join(str(cpu) for cpu in loc['cpus'])) |
79939c4a KYL |
232 | gdb.write("\n") |
233 | if constants.LX_CONFIG_STACKDEPOT: | |
234 | if loc['handle']: | |
235 | stackdepot.stack_depot_print(loc['handle']) | |
236 | gdb.write("\n") | |
237 | ||
238 | def help(): | |
239 | t = """Usage: lx-slabtrace --cache_name [cache_name] [Options] | |
240 | Options: | |
241 | --alloc | |
242 | print information of allocation trace of the allocated objects | |
243 | --free | |
244 | print information of freeing trace of the allocated objects | |
245 | Example: | |
246 | lx-slabtrace --cache_name kmalloc-1k --alloc | |
247 | lx-slabtrace --cache_name kmalloc-1k --free\n""" | |
248 | gdb.write("Unrecognized command\n") | |
249 | raise gdb.GdbError(t) | |
250 | ||
251 | class LxSlabTrace(gdb.Command): | |
252 | """Show specific cache slabtrace""" | |
253 | ||
254 | def __init__(self): | |
255 | super(LxSlabTrace, self).__init__("lx-slabtrace", gdb.COMMAND_DATA) | |
256 | ||
257 | def invoke(self, arg, from_tty): | |
258 | if not constants.LX_CONFIG_SLUB_DEBUG: | |
259 | raise gdb.GdbError("CONFIG_SLUB_DEBUG is not enabled") | |
260 | ||
261 | argv = gdb.string_to_argv(arg) | |
262 | alloc = track_alloc # default show alloc_traces | |
263 | ||
264 | if len(argv) == 3: | |
265 | if argv[2] == '--alloc': | |
266 | alloc = track_alloc | |
267 | elif argv[2] == '--free': | |
268 | alloc = track_free | |
269 | else: | |
270 | help() | |
271 | if len(argv) >= 2 and argv[0] == '--cache_name': | |
272 | slabtrace(alloc, argv[1]) | |
273 | else: | |
274 | help() | |
275 | LxSlabTrace() | |
276 | ||
277 | def slabinfo(): | |
278 | nr_node_ids = None | |
279 | ||
280 | if not constants.LX_CONFIG_SLUB_DEBUG: | |
281 | raise gdb.GdbError("CONFIG_SLUB_DEBUG is not enabled") | |
282 | ||
283 | def count_free(slab): | |
284 | total_free = 0 | |
285 | for slab in lists.list_for_each_entry(slab, slab_ptr_type, 'slab_list'): | |
286 | total_free += int(slab['objects'] - slab['inuse']) | |
287 | return total_free | |
288 | ||
289 | gdb.write("{:^18} | {:^20} | {:^12} | {:^12} | {:^8} | {:^11} | {:^13}\n".format('Pointer', 'name', 'active_objs', 'num_objs', 'objsize', 'objperslab', 'pagesperslab')) | |
290 | gdb.write("{:-^18} | {:-^20} | {:-^12} | {:-^12} | {:-^8} | {:-^11} | {:-^13}\n".format('', '', '', '', '', '', '')) | |
291 | ||
292 | slab_caches = gdb.parse_and_eval("slab_caches") | |
293 | if mm.page_ops().ops.MAX_NUMNODES > 1: | |
294 | nr_node_ids = int(gdb.parse_and_eval("nr_node_ids")) | |
295 | else: | |
296 | nr_node_ids = 1 | |
297 | ||
298 | for cache in lists.list_for_each_entry(slab_caches, kmem_cache_ptr_type, 'list'): | |
299 | nr_objs = 0 | |
300 | nr_free = 0 | |
301 | nr_slabs = 0 | |
302 | for i in range(0, nr_node_ids): | |
303 | cache_node = cache['node'][i] | |
304 | try: | |
305 | nr_slabs += cache_node['nr_slabs']['counter'] | |
306 | nr_objs = int(cache_node['total_objects']['counter']) | |
307 | nr_free = count_free(cache_node['partial']) | |
308 | except: | |
309 | raise gdb.GdbError(traceback.format_exc()) | |
310 | active_objs = nr_objs - nr_free | |
311 | num_objs = nr_objs | |
312 | active_slabs = nr_slabs | |
313 | objects_per_slab = oo_objects(cache['oo']) | |
314 | cache_order = oo_order(cache['oo']) | |
315 | gdb.write("{:18s} | {:20.19s} | {:12} | {:12} | {:8} | {:11} | {:13}\n".format(hex(cache), cache['name'].string(), str(active_objs), str(num_objs), str(cache['size']), str(objects_per_slab), str(1 << cache_order))) | |
316 | ||
317 | class LxSlabInfo(gdb.Command): | |
318 | """Show slabinfo""" | |
319 | ||
320 | def __init__(self): | |
321 | super(LxSlabInfo, self).__init__("lx-slabinfo", gdb.COMMAND_DATA) | |
322 | ||
323 | def invoke(self, arg, from_tty): | |
324 | slabinfo() | |
325 | LxSlabInfo() |