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

scripts/gdb/slab: add slab support

Add 'lx-slabinfo' and 'lx-slabtrace' support.

This GDB scripts print slabinfo and slabtrace for user
to analyze slab memory usage.

Example output like below:
(gdb) lx-slabinfo
Pointer | name | active_objs | num_objs | objsize | objperslab | pagesperslab
------------------ | -------------------- | ------------ | ------------ | -------- | ----------- | -------------
0xffff0000c59df480 | p9_req_t | 0 | 0 | 280 | 29 | 2
0xffff0000c59df280 | isp1760_qh | 0 | 0 | 160 | 25 | 1
0xffff0000c59df080 | isp1760_qtd | 0 | 0 | 184 | 22 | 1
0xffff0000c59dee80 | isp1760_urb_listite | 0 | 0 | 136 | 30 | 1
0xffff0000c59dec80 | asd_sas_event | 0 | 0 | 256 | 32 | 2
0xffff0000c59dea80 | sas_task | 0 | 0 | 448 | 36 | 4
0xffff0000c59de880 | bio-120 | 18 | 21 | 384 | 21 | 2
0xffff0000c59de680 | io_kiocb | 0 | 0 | 448 | 36 | 4
0xffff0000c59de480 | bfq_io_cq | 0 | 0 | 1504 | 21 | 8
0xffff0000c59de280 | bfq_queue | 0 | 0 | 720 | 22 | 4
0xffff0000c59de080 | mqueue_inode_cache | 1 | 28 | 1152 | 28 | 8
0xffff0000c59dde80 | v9fs_inode_cache | 0 | 0 | 832 | 39 | 8
...

(gdb) lx-slabtrace --cache_name kmalloc-1k
63 <tty_register_device_attr+508> waste=16632/264 age=46856/46871/46888 pid=1 cpus=6,
0xffff800008720240 <__kmem_cache_alloc_node+236>: mov x22, x0
0xffff80000862a4fc <kmalloc_trace+64>: mov x21, x0
0xffff8000095d086c <tty_register_device_attr+508>: mov x19, x0
0xffff8000095d0f98 <tty_register_driver+704>: cmn x0, #0x1, lsl #12
0xffff80000c2677e8 <vty_init+620>: Cannot access memory at address 0xffff80000c2677e8
0xffff80000c265a10 <tty_init+276>: Cannot access memory at address 0xffff80000c265a10
0xffff80000c26d3c4 <chr_dev_init+204>: Cannot access memory at address 0xffff80000c26d3c4
0xffff8000080161d4 <do_one_initcall+176>: mov w21, w0
0xffff80000c1c1b58 <kernel_init_freeable+956>: Cannot access memory at address 0xffff80000c1c1b58
0xffff80000acf1334 <kernel_init+36>: bl 0xffff8000081ac040 <async_synchronize_full>
0xffff800008018d00 <ret_from_fork+16>: mrs x28, sp_el0

(gdb) lx-slabtrace --cache_name kmalloc-1k --free
428 <not-available> age=4294958600 pid=0 cpus=0,

Link: https://lkml.kernel.org/r/20230808083020.22254-8-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
79939c4a 2f060190

+340
+13
scripts/gdb/linux/constants.py.in
··· 20 20 #include <linux/of_fdt.h> 21 21 #include <linux/page_ext.h> 22 22 #include <linux/radix-tree.h> 23 + #include <linux/slab.h> 23 24 #include <linux/threads.h> 24 25 25 26 /* We need to stringify expanded macros so that they can be parsed */ ··· 96 95 LX_GDBPARSED(PAGE_EXT_OWNER) 97 96 LX_GDBPARSED(PAGE_EXT_OWNER_ALLOCATED) 98 97 98 + /* linux/slab.h */ 99 + LX_GDBPARSED(SLAB_RED_ZONE) 100 + LX_GDBPARSED(SLAB_POISON) 101 + LX_GDBPARSED(SLAB_KMALLOC) 102 + LX_GDBPARSED(SLAB_HWCACHE_ALIGN) 103 + LX_GDBPARSED(SLAB_CACHE_DMA) 104 + LX_GDBPARSED(SLAB_CACHE_DMA32) 105 + LX_GDBPARSED(SLAB_STORE_USER) 106 + LX_GDBPARSED(SLAB_PANIC) 107 + 99 108 /* Kernel Configs */ 100 109 LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS) 101 110 LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) ··· 147 136 LX_CONFIG(CONFIG_DEBUG_VIRTUAL) 148 137 LX_CONFIG(CONFIG_STACKDEPOT) 149 138 LX_CONFIG(CONFIG_PAGE_OWNER) 139 + LX_CONFIG(CONFIG_SLUB_DEBUG) 140 + LX_CONFIG(CONFIG_SLAB_FREELIST_HARDENED)
+326
scripts/gdb/linux/slab.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 + 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=") 231 + for i in loc['cpus']: 232 + gdb.write("%d," % i) 233 + gdb.write("\n") 234 + if constants.LX_CONFIG_STACKDEPOT: 235 + if loc['handle']: 236 + stackdepot.stack_depot_print(loc['handle']) 237 + gdb.write("\n") 238 + 239 + def help(): 240 + t = """Usage: lx-slabtrace --cache_name [cache_name] [Options] 241 + Options: 242 + --alloc 243 + print information of allocation trace of the allocated objects 244 + --free 245 + print information of freeing trace of the allocated objects 246 + Example: 247 + lx-slabtrace --cache_name kmalloc-1k --alloc 248 + lx-slabtrace --cache_name kmalloc-1k --free\n""" 249 + gdb.write("Unrecognized command\n") 250 + raise gdb.GdbError(t) 251 + 252 + class LxSlabTrace(gdb.Command): 253 + """Show specific cache slabtrace""" 254 + 255 + def __init__(self): 256 + super(LxSlabTrace, self).__init__("lx-slabtrace", gdb.COMMAND_DATA) 257 + 258 + def invoke(self, arg, from_tty): 259 + if not constants.LX_CONFIG_SLUB_DEBUG: 260 + raise gdb.GdbError("CONFIG_SLUB_DEBUG is not enabled") 261 + 262 + argv = gdb.string_to_argv(arg) 263 + alloc = track_alloc # default show alloc_traces 264 + 265 + if len(argv) == 3: 266 + if argv[2] == '--alloc': 267 + alloc = track_alloc 268 + elif argv[2] == '--free': 269 + alloc = track_free 270 + else: 271 + help() 272 + if len(argv) >= 2 and argv[0] == '--cache_name': 273 + slabtrace(alloc, argv[1]) 274 + else: 275 + help() 276 + LxSlabTrace() 277 + 278 + def slabinfo(): 279 + nr_node_ids = None 280 + 281 + if not constants.LX_CONFIG_SLUB_DEBUG: 282 + raise gdb.GdbError("CONFIG_SLUB_DEBUG is not enabled") 283 + 284 + def count_free(slab): 285 + total_free = 0 286 + for slab in lists.list_for_each_entry(slab, slab_ptr_type, 'slab_list'): 287 + total_free += int(slab['objects'] - slab['inuse']) 288 + return total_free 289 + 290 + gdb.write("{:^18} | {:^20} | {:^12} | {:^12} | {:^8} | {:^11} | {:^13}\n".format('Pointer', 'name', 'active_objs', 'num_objs', 'objsize', 'objperslab', 'pagesperslab')) 291 + gdb.write("{:-^18} | {:-^20} | {:-^12} | {:-^12} | {:-^8} | {:-^11} | {:-^13}\n".format('', '', '', '', '', '', '')) 292 + 293 + slab_caches = gdb.parse_and_eval("slab_caches") 294 + if mm.page_ops().ops.MAX_NUMNODES > 1: 295 + nr_node_ids = int(gdb.parse_and_eval("nr_node_ids")) 296 + else: 297 + nr_node_ids = 1 298 + 299 + for cache in lists.list_for_each_entry(slab_caches, kmem_cache_ptr_type, 'list'): 300 + nr_objs = 0 301 + nr_free = 0 302 + nr_slabs = 0 303 + for i in range(0, nr_node_ids): 304 + cache_node = cache['node'][i] 305 + try: 306 + nr_slabs += cache_node['nr_slabs']['counter'] 307 + nr_objs = int(cache_node['total_objects']['counter']) 308 + nr_free = count_free(cache_node['partial']) 309 + except: 310 + raise gdb.GdbError(traceback.format_exc()) 311 + active_objs = nr_objs - nr_free 312 + num_objs = nr_objs 313 + active_slabs = nr_slabs 314 + objects_per_slab = oo_objects(cache['oo']) 315 + cache_order = oo_order(cache['oo']) 316 + 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))) 317 + 318 + class LxSlabInfo(gdb.Command): 319 + """Show slabinfo""" 320 + 321 + def __init__(self): 322 + super(LxSlabInfo, self).__init__("lx-slabinfo", gdb.COMMAND_DATA) 323 + 324 + def invoke(self, arg, from_tty): 325 + slabinfo() 326 + LxSlabInfo()
+1
scripts/gdb/vmlinux-gdb.py
··· 47 47 import linux.mm 48 48 import linux.stackdepot 49 49 import linux.page_owner 50 + import linux.slab