def objs(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct kmem_cache *' count: int = cache.node[0].total_objects.counter.value_() if is_root_cache(cache): for child in for_each_child_cache(cache): count += objs(child) return count
def for_each_slab_flag_in_cache(cache: drgn.Object) -> Iterable[str]: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' flag = cache.skc_flags.value_() for enum_entry, enum_entry_bit in cache.prog_.type( 'enum kmc_bit').enumerators: if flag & (1 << enum_entry_bit): yield enum_entry.replace('_BIT', '')
def nr_slabs(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct kmem_cache *' nslabs: int = cache.node[0].nr_slabs.counter.value_() if is_root_cache(cache): for child in for_each_child_cache(cache): nslabs += nr_slabs(child) return nslabs
def _call(self, objs: Iterable[drgn.Object]) -> None: baked = { sdb.type_canonicalize_name(type_): class_ for type_, class_ in sdb.PrettyPrinter.all_printers.items() } handling_class = None first_obj_type, objs = sdb.get_first_type(objs) if first_obj_type is not None: first_obj_type_name = sdb.type_canonical_name(first_obj_type) if first_obj_type_name in baked: handling_class = baked[first_obj_type_name] if handling_class is None: if first_obj_type is not None: msg = 'could not find pretty-printer for type {}\n'.format( first_obj_type) else: msg = 'could not find pretty-printer\n' msg += "The following types have pretty-printers:\n" msg += f"\t{'PRINTER':<20s} {'TYPE':<20s}\n" for type_name, class_ in sdb.PrettyPrinter.all_printers.items(): msg += f"\t{class_.names[0]:<20s} {type_name:<20s}\n" raise sdb.CommandError(self.name, msg) handling_class().pretty_print(objs)
def entry_size(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' if backed_by_linux_cache(cache): return slub.entry_size(cache.skc_linux_cache) ops = objs_per_slab(cache) if ops == 0: return 0 return int(slab_size(cache) / objs_per_slab(cache))
def slab_linux_cache_source(cache: drgn.Object) -> str: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' if not backed_by_linux_cache(cache): name = slab_name(cache) subsystem = "SPL" else: name = cache.skc_linux_cache.name.string_().decode('utf-8') subsystem = "SLUB" return f"{name}[{subsystem:4}]"
def inactive_objs(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct kmem_cache *' node = cache.node[0].partial # assumption nr_node_ids == 0 free = 0 for page in list_for_each_entry("struct page", node.address_of_(), "lru"): free += page.objects.value_() - page.inuse.value_() if is_root_cache(cache): for child in for_each_child_cache(cache): free += inactive_objs(child) return free
def for_each_partial_slab_in_cache( cache: drgn.Object) -> Iterable[drgn.Object]: assert sdb.type_canonical_name(cache.type_) == 'struct kmem_cache *' node = cache.node[0].partial # assumption nr_node_ids == 0 yield from list_for_each_entry("struct page", node.address_of_(), "lru") if is_root_cache(cache): for child in for_each_child_cache(cache): yield from for_each_partial_slab_in_cache(child)
def for_each_partial_slab_in_cache( cache: drgn.Object) -> Iterable[drgn.Object]: assert sdb.type_canonical_name(cache.type_) == 'struct kmem_cache *' for node in for_each_node(cache): node_partial = node.partial yield from list_for_each_entry("struct page", node_partial.address_of_(), "lru") if is_root_cache(cache): for child in for_each_child_cache(cache): yield from for_each_partial_slab_in_cache(child)
def cache_get_free_pointer(cache: drgn.Object, p: drgn.Object) -> drgn.Object: """ Get the next pointer in the freelist. Note, that this function assumes that CONFIG_SLAB_FREELIST_HARDENED is set in the target """ assert sdb.type_canonical_name(cache.type_) == 'struct kmem_cache *' assert sdb.type_canonical_name(p.type_) == 'void *' hardened_ptr = p + cache.offset.value_() # # We basically do what `freelist_dereference()` and # `freelist_ptr()` do in the kernel source: # # ptr <- (void *)*(unsigned long *)(hardened_ptr) # intermediate_ulong = drgn.Object(sdb.get_prog(), type='unsigned long', address=hardened_ptr.value_()) ptr = drgn.Object(sdb.get_prog(), type='void *', value=intermediate_ulong.value_()) # # ptr_addr <- (unsigned long)hardened_ptr # ptr_addr = drgn.Object(sdb.get_prog(), type='unsigned long', value=hardened_ptr.value_()) # # return (void *)((unsigned long)ptr ^ cache->random ^ ptr_addr) # ptr_as_ulong = drgn.Object(sdb.get_prog(), type='unsigned long', value=ptr.value_()) clean_ptr_val = ptr_as_ulong.value_() clean_ptr_val ^= cache.random.value_() clean_ptr_val ^= ptr_addr.value_() return drgn.Object(sdb.get_prog(), type='void *', value=clean_ptr_val)
def obj_alloc(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' if backed_by_linux_cache(cache): try: return int(drgn_percpu.percpu_counter_sum(cache.skc_linux_alloc)) except AttributeError: # # The percpu_counter referenced above wasn't in ZoL until the # following commit: ec1fea4516ac2f0c08d31d6308929298d1b281d0 # # Fall back to the old-mechanism of using skc_obj_alloc if that # percpu_counter member doesn't exist (an AttributeError will # be thrown). # pass return int(cache.skc_obj_alloc.value_())
def for_each_object_in_spl_cache(cache: drgn.Object) -> Iterable[drgn.Object]: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' # # ZFSonLinux initially implemented OFFSLAB caches for certain cases # that never showed up and thus have never been used in practice. # Ensure here that we are not looking at such a cache. # if 'KMC_OFFSLAB' in list(for_each_slab_flag_in_cache(cache)): raise sdb.CommandError("spl_caches", "KMC_OFFSLAB caches are not supported") for slab_list in [cache.skc_complete_list, cache.skc_partial_list]: for slab in drgn_list.list_for_each_entry("spl_kmem_slab_t", slab_list.address_of_(), "sks_list"): yield from for_each_onslab_object_in_slab(slab)
def for_each_onslab_object_in_slab(slab: drgn.Object) -> Iterable[drgn.Object]: assert sdb.type_canonical_name(slab.type_) == 'struct spl_kmem_slab *' cache = slab.sks_cache sks_size = spl_aligned_slab_size(cache) spl_obj_size = spl_aligned_obj_size(cache) for i in range(slab.sks_objs.value_()): obj = sdb.create_object('void *', slab.value_() + sks_size + (i * spl_obj_size)) # # If the sko_list of the object is empty, it means that # this object is not part of the slab's internal free list # and therefore it is allocated. NOTE: sko_list in the # actual code is not a list, but a link on a list. Thus, # the check below is not checking whether the "object # list" is empty for this slab, but rather whether the # link is part of any list. # sko = sko_from_obj(cache, obj) assert sko.sko_magic.value_() == 0x20202020 # SKO_MAGIC if linked_lists.is_list_empty(sko.sko_list): yield obj
def object_size(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct kmem_cache *' return int(cache.object_size.value_())
def slab_size(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' return int(cache.skc_slab_size.value_())
def slab_name(cache: drgn.Object) -> str: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' return str(cache.skc_name.string_().decode('utf-8'))
def backed_by_linux_cache(cache: drgn.Object) -> bool: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' return int(cache.skc_linux_cache.value_()) != 0x0
def sko_from_obj(cache: drgn.Object, obj: drgn.Object) -> drgn.Object: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' cache_obj_align = cache.skc_obj_align.value_() return sdb.create_object( 'spl_kmem_obj_t *', obj.value_() + p2.p2roundup(object_size(cache), cache_obj_align))
def spl_aligned_slab_size(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' cache_obj_align = cache.skc_obj_align.value_() spl_slab_type_size = sdb.type_canonicalize_size('spl_kmem_slab_t') return p2.p2roundup(spl_slab_type_size, cache_obj_align)
def total_memory(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct kmem_cache *' nslabs = nr_slabs(cache) epslab = entries_per_slab(cache) esize = entry_size(cache) return nslabs * epslab * esize
def is_list_empty(l: drgn.Object) -> bool: """ True if list is empty, False otherwise. """ assert sdb.type_canonical_name(l.type_) == 'struct list_head' return int(l.address_of_().value_()) == int(l.next.value_())
def nr_objects(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' if backed_by_linux_cache(cache): return int(cache.skc_obj_alloc.value_()) return int(cache.skc_obj_total.value_())
def active_objs(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct kmem_cache *' return objs(cache) - inactive_objs(cache)
def obj_inactive(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' return nr_objects(cache) - obj_alloc(cache)
def slab_flags(cache: drgn.Object) -> str: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' return '|'.join(for_each_slab_flag_in_cache(cache))
def active_memory(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' return obj_alloc(cache) * entry_size(cache)
def obj_alloc(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' return int(cache.skc_obj_alloc.value_())
def total_memory(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' if backed_by_linux_cache(cache): return slub.total_memory(cache.skc_linux_cache) return slab_size(cache) * nr_slabs(cache)
def get_valid_type_by_name(cmd: sdb.Command, tname: str) -> drgn.Type: """ Given a type name in string form (`tname`) without any C keyword prefixes (e.g. 'struct', 'enum', 'class', 'union'), return the corresponding drgn.Type object. This function is used primarily by commands that accept a type name as an argument and exist only to save keystrokes for the user. """ if tname in ['struct', 'enum', 'union', 'class']: # # Note: We have to do this because currently in drgn # prog.type('struct') returns a different error than # prog.type('bogus'). The former returns a SyntaxError # "null identifier" while the latter returns LookupError # "could not find typedef bogus". The former is not # user-friendly and thus we just avoid that situation # by instructing the user to skip such keywords. # raise sdb.CommandError( cmd.name, f"skip keyword '{tname}' or quote your type \"{tname} <typename>\"") try: type_ = sdb.get_type(tname) if type_.kind == drgn.TypeKind.TYPEDEF and type_.type_name( ) == sdb.type_canonical_name(type_): # # In some C codebases there are typedefs like this: # # typedef union GCObject GCObject; // taken from LUA repo # # The point of the above is to avoid typing the word # 'union' every time we declare a variable of that type. # For the purposes of SDB, passing around a drng.Type # describing the typedef above isn't particularly # useful. Using such an object with the `ptype` command # (one of the consumers of this function) would yield # the following: # # sdb> ptype GCObject # typedef union GCObject GCObject # # Resolving the typedef's explicitly in those cases # is more useful and this is why this if-clause exists. # # sdb> ptype GCObject # union GCObject { # GCheader gch; # union TString ts; # ... # } # return sdb.type_canonicalize(type_) return type_ except LookupError: # # We couldn't find a type with that name. Check if # it is a structure, an enum, or a union. # pass for prefix in ["struct ", "enum ", "union "]: try: return sdb.get_type(f"{prefix}{tname}") except LookupError: pass raise sdb.CommandError( cmd.name, f"couldn't find typedef, struct, enum, nor union named '{tname}'")
def util(cache: drgn.Object) -> int: assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *' total_mem = total_memory(cache) if total_mem == 0: return 0 return int((active_memory(cache) / total_mem) * 100)