| # SPDX-License-Identifier: GPL-2.0 |
| # |
| # Copyright (c) 2023 MediaTek Inc. |
| # |
| # Authors: |
| # Kuan-Ying Lee <[email protected]> |
| # |
| |
| import gdb |
| import re |
| import traceback |
| from linux import lists, utils, stackdepot, constants, mm |
| |
| SLAB_RED_ZONE = constants.LX_SLAB_RED_ZONE |
| SLAB_POISON = constants.LX_SLAB_POISON |
| SLAB_KMALLOC = constants.LX_SLAB_KMALLOC |
| SLAB_HWCACHE_ALIGN = constants.LX_SLAB_HWCACHE_ALIGN |
| SLAB_CACHE_DMA = constants.LX_SLAB_CACHE_DMA |
| SLAB_CACHE_DMA32 = constants.LX_SLAB_CACHE_DMA32 |
| SLAB_STORE_USER = constants.LX_SLAB_STORE_USER |
| SLAB_PANIC = constants.LX_SLAB_PANIC |
| |
| OO_SHIFT = 16 |
| OO_MASK = (1 << OO_SHIFT) - 1 |
| |
| if constants.LX_CONFIG_SLUB_DEBUG: |
| slab_type = utils.CachedType("struct slab") |
| slab_ptr_type = slab_type.get_type().pointer() |
| kmem_cache_type = utils.CachedType("struct kmem_cache") |
| kmem_cache_ptr_type = kmem_cache_type.get_type().pointer() |
| freeptr_t = utils.CachedType("freeptr_t") |
| freeptr_t_ptr = freeptr_t.get_type().pointer() |
| |
| track_type = gdb.lookup_type('struct track') |
| track_alloc = int(gdb.parse_and_eval('TRACK_ALLOC')) |
| track_free = int(gdb.parse_and_eval('TRACK_FREE')) |
| |
| def slab_folio(slab): |
| return slab.cast(gdb.lookup_type("struct folio").pointer()) |
| |
| def slab_address(slab): |
| p_ops = mm.page_ops().ops |
| folio = slab_folio(slab) |
| return p_ops.folio_address(folio) |
| |
| def for_each_object(cache, addr, slab_objects): |
| p = addr |
| if cache['flags'] & SLAB_RED_ZONE: |
| p += int(cache['red_left_pad']) |
| while p < addr + (slab_objects * cache['size']): |
| yield p |
| p = p + int(cache['size']) |
| |
| def get_info_end(cache): |
| if (cache['offset'] >= cache['inuse']): |
| return cache['inuse'] + gdb.lookup_type("void").pointer().sizeof |
| else: |
| return cache['inuse'] |
| |
| def get_orig_size(cache, obj): |
| if cache['flags'] & SLAB_STORE_USER and cache['flags'] & SLAB_KMALLOC: |
| p = mm.page_ops().ops.kasan_reset_tag(obj) |
| p += get_info_end(cache) |
| p += gdb.lookup_type('struct track').sizeof * 2 |
| p = p.cast(utils.get_uint_type().pointer()) |
| return p.dereference() |
| else: |
| return cache['object_size'] |
| |
| def get_track(cache, object_pointer, alloc): |
| p = object_pointer + get_info_end(cache) |
| p += (alloc * track_type.sizeof) |
| return p |
| |
| def oo_objects(x): |
| return int(x['x']) & OO_MASK |
| |
| def oo_order(x): |
| return int(x['x']) >> OO_SHIFT |
| |
| def reciprocal_divide(a, R): |
| t = (a * int(R['m'])) >> 32 |
| return (t + ((a - t) >> int(R['sh1']))) >> int(R['sh2']) |
| |
| def __obj_to_index(cache, addr, obj): |
| return reciprocal_divide(int(mm.page_ops().ops.kasan_reset_tag(obj)) - addr, cache['reciprocal_size']) |
| |
| def swab64(x): |
| result = (((x & 0x00000000000000ff) << 56) | \ |
| ((x & 0x000000000000ff00) << 40) | \ |
| ((x & 0x0000000000ff0000) << 24) | \ |
| ((x & 0x00000000ff000000) << 8) | \ |
| ((x & 0x000000ff00000000) >> 8) | \ |
| ((x & 0x0000ff0000000000) >> 24) | \ |
| ((x & 0x00ff000000000000) >> 40) | \ |
| ((x & 0xff00000000000000) >> 56)) |
| return result |
| |
| def freelist_ptr_decode(cache, ptr, ptr_addr): |
| if constants.LX_CONFIG_SLAB_FREELIST_HARDENED: |
| return ptr['v'] ^ cache['random'] ^ swab64(int(ptr_addr)) |
| else: |
| return ptr['v'] |
| |
| def get_freepointer(cache, obj): |
| obj = mm.page_ops().ops.kasan_reset_tag(obj) |
| ptr_addr = obj + cache['offset'] |
| p = ptr_addr.cast(freeptr_t_ptr).dereference() |
| return freelist_ptr_decode(cache, p, ptr_addr) |
| |
| def loc_exist(loc_track, addr, handle, waste): |
| for loc in loc_track: |
| if loc['addr'] == addr and loc['handle'] == handle and loc['waste'] == waste: |
| return loc |
| return None |
| |
| def add_location(loc_track, cache, track, orig_size): |
| jiffies = gdb.parse_and_eval("jiffies_64") |
| age = jiffies - track['when'] |
| handle = 0 |
| waste = cache['object_size'] - int(orig_size) |
| pid = int(track['pid']) |
| cpuid = int(track['cpu']) |
| addr = track['addr'] |
| if constants.LX_CONFIG_STACKDEPOT: |
| handle = track['handle'] |
| |
| loc = loc_exist(loc_track, addr, handle, waste) |
| if loc: |
| loc['count'] += 1 |
| if track['when']: |
| loc['sum_time'] += age |
| loc['min_time'] = min(loc['min_time'], age) |
| loc['max_time'] = max(loc['max_time'], age) |
| loc['min_pid'] = min(loc['min_pid'], pid) |
| loc['max_pid'] = max(loc['max_pid'], pid) |
| loc['cpus'].add(cpuid) |
| else: |
| loc_track.append({ |
| 'count' : 1, |
| 'addr' : addr, |
| 'sum_time' : age, |
| 'min_time' : age, |
| 'max_time' : age, |
| 'min_pid' : pid, |
| 'max_pid' : pid, |
| 'handle' : handle, |
| 'waste' : waste, |
| 'cpus' : {cpuid} |
| } |
| ) |
| |
| def slabtrace(alloc, cache_name): |
| |
| def __fill_map(obj_map, cache, slab): |
| p = slab['freelist'] |
| addr = slab_address(slab) |
| while p != gdb.Value(0): |
| index = __obj_to_index(cache, addr, p) |
| obj_map[index] = True # free objects |
| p = get_freepointer(cache, p) |
| |
| # process every slab page on the slab_list (partial and full list) |
| def process_slab(loc_track, slab_list, alloc, cache): |
| for slab in lists.list_for_each_entry(slab_list, slab_ptr_type, "slab_list"): |
| obj_map[:] = [False] * oo_objects(cache['oo']) |
| __fill_map(obj_map, cache, slab) |
| addr = slab_address(slab) |
| for object_pointer in for_each_object(cache, addr, slab['objects']): |
| if obj_map[__obj_to_index(cache, addr, object_pointer)] == True: |
| continue |
| p = get_track(cache, object_pointer, alloc) |
| track = gdb.Value(p).cast(track_type.pointer()) |
| if alloc == track_alloc: |
| size = get_orig_size(cache, object_pointer) |
| else: |
| size = cache['object_size'] |
| add_location(loc_track, cache, track, size) |
| continue |
| |
| slab_caches = gdb.parse_and_eval("slab_caches") |
| if mm.page_ops().ops.MAX_NUMNODES > 1: |
| nr_node_ids = int(gdb.parse_and_eval("nr_node_ids")) |
| else: |
| nr_node_ids = 1 |
| |
| target_cache = None |
| loc_track = [] |
| |
| for cache in lists.list_for_each_entry(slab_caches, kmem_cache_ptr_type, 'list'): |
| if cache['name'].string() == cache_name: |
| target_cache = cache |
| break |
| |
| obj_map = [False] * oo_objects(target_cache['oo']) |
| |
| if target_cache['flags'] & SLAB_STORE_USER: |
| for i in range(0, nr_node_ids): |
| cache_node = target_cache['node'][i] |
| if cache_node['nr_slabs']['counter'] == 0: |
| continue |
| process_slab(loc_track, cache_node['partial'], alloc, target_cache) |
| process_slab(loc_track, cache_node['full'], alloc, target_cache) |
| else: |
| raise gdb.GdbError("SLAB_STORE_USER is not set in %s" % target_cache['name'].string()) |
| |
| for loc in sorted(loc_track, key=lambda x:x['count'], reverse=True): |
| if loc['addr']: |
| addr = loc['addr'].cast(utils.get_ulong_type().pointer()) |
| gdb.write("%d %s" % (loc['count'], str(addr).split(' ')[-1])) |
| else: |
| gdb.write("%d <not-available>" % loc['count']) |
| |
| if loc['waste']: |
| gdb.write(" waste=%d/%d" % (loc['count'] * loc['waste'], loc['waste'])) |
| |
| if loc['sum_time'] != loc['min_time']: |
| gdb.write(" age=%d/%d/%d" % (loc['min_time'], loc['sum_time']/loc['count'], loc['max_time'])) |
| else: |
| gdb.write(" age=%d" % loc['min_time']) |
| |
| if loc['min_pid'] != loc['max_pid']: |
| gdb.write(" pid=%d-%d" % (loc['min_pid'], loc['max_pid'])) |
| else: |
| gdb.write(" pid=%d" % loc['min_pid']) |
| |
| if constants.LX_NR_CPUS > 1: |
| nr_cpu = gdb.parse_and_eval('__num_online_cpus')['counter'] |
| if nr_cpu > 1: |
| gdb.write(" cpus=") |
| for i in loc['cpus']: |
| gdb.write("%d," % i) |
| gdb.write("\n") |
| if constants.LX_CONFIG_STACKDEPOT: |
| if loc['handle']: |
| stackdepot.stack_depot_print(loc['handle']) |
| gdb.write("\n") |
| |
| def help(): |
| t = """Usage: lx-slabtrace --cache_name [cache_name] [Options] |
| Options: |
| --alloc |
| print information of allocation trace of the allocated objects |
| --free |
| print information of freeing trace of the allocated objects |
| Example: |
| lx-slabtrace --cache_name kmalloc-1k --alloc |
| lx-slabtrace --cache_name kmalloc-1k --free\n""" |
| gdb.write("Unrecognized command\n") |
| raise gdb.GdbError(t) |
| |
| class LxSlabTrace(gdb.Command): |
| """Show specific cache slabtrace""" |
| |
| def __init__(self): |
| super(LxSlabTrace, self).__init__("lx-slabtrace", gdb.COMMAND_DATA) |
| |
| def invoke(self, arg, from_tty): |
| if not constants.LX_CONFIG_SLUB_DEBUG: |
| raise gdb.GdbError("CONFIG_SLUB_DEBUG is not enabled") |
| |
| argv = gdb.string_to_argv(arg) |
| alloc = track_alloc # default show alloc_traces |
| |
| if len(argv) == 3: |
| if argv[2] == '--alloc': |
| alloc = track_alloc |
| elif argv[2] == '--free': |
| alloc = track_free |
| else: |
| help() |
| if len(argv) >= 2 and argv[0] == '--cache_name': |
| slabtrace(alloc, argv[1]) |
| else: |
| help() |
| LxSlabTrace() |
| |
| def slabinfo(): |
| nr_node_ids = None |
| |
| if not constants.LX_CONFIG_SLUB_DEBUG: |
| raise gdb.GdbError("CONFIG_SLUB_DEBUG is not enabled") |
| |
| def count_free(slab): |
| total_free = 0 |
| for slab in lists.list_for_each_entry(slab, slab_ptr_type, 'slab_list'): |
| total_free += int(slab['objects'] - slab['inuse']) |
| return total_free |
| |
| gdb.write("{:^18} | {:^20} | {:^12} | {:^12} | {:^8} | {:^11} | {:^13}\n".format('Pointer', 'name', 'active_objs', 'num_objs', 'objsize', 'objperslab', 'pagesperslab')) |
| gdb.write("{:-^18} | {:-^20} | {:-^12} | {:-^12} | {:-^8} | {:-^11} | {:-^13}\n".format('', '', '', '', '', '', '')) |
| |
| slab_caches = gdb.parse_and_eval("slab_caches") |
| if mm.page_ops().ops.MAX_NUMNODES > 1: |
| nr_node_ids = int(gdb.parse_and_eval("nr_node_ids")) |
| else: |
| nr_node_ids = 1 |
| |
| for cache in lists.list_for_each_entry(slab_caches, kmem_cache_ptr_type, 'list'): |
| nr_objs = 0 |
| nr_free = 0 |
| nr_slabs = 0 |
| for i in range(0, nr_node_ids): |
| cache_node = cache['node'][i] |
| try: |
| nr_slabs += cache_node['nr_slabs']['counter'] |
| nr_objs = int(cache_node['total_objects']['counter']) |
| nr_free = count_free(cache_node['partial']) |
| except: |
| raise gdb.GdbError(traceback.format_exc()) |
| active_objs = nr_objs - nr_free |
| num_objs = nr_objs |
| active_slabs = nr_slabs |
| objects_per_slab = oo_objects(cache['oo']) |
| cache_order = oo_order(cache['oo']) |
| 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))) |
| |
| class LxSlabInfo(gdb.Command): |
| """Show slabinfo""" |
| |
| def __init__(self): |
| super(LxSlabInfo, self).__init__("lx-slabinfo", gdb.COMMAND_DATA) |
| |
| def invoke(self, arg, from_tty): |
| slabinfo() |
| LxSlabInfo() |