def inactive_objs(cache: drgn.Object) -> int: assert cache.type_.type_name() == 'struct kmem_cache *' node = cache.node[0].partial free = 0 for page in list_for_each_entry("struct page", node.address_of_(), "lru"): free += page.objects.value_() - page.inuse.value_() if not is_root_cache(cache): return free for child in list_for_each_entry("struct kmem_cache", cache.memcg_params.children.address_of_(), "memcg_params.children_node"): free += inactive_objs(child) return free
def _call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]: sname = get_valid_struct_name(self, self.args.struct_name) for obj in objs: try: yield from list_for_each_entry(sname, obj, self.args.member) except LookupError as err: raise sdb.CommandError(self.name, str(err))
def css_next_child(pos: Object, parent: Object) -> Object: """ Get the next child (or ``NULL`` if there is none) of the given parent starting from the given position (``NULL`` to initiate traversal). :param pos: ``struct cgroup_subsys_state *`` :param parent: ``struct cgroup_subsys_state *`` :return: ``struct cgroup_subsys_state *`` """ if not pos: next_ = container_of( parent.children.next, "struct cgroup_subsys_state", "sibling" ) elif not (pos.flags & pos.prog_["CSS_RELEASED"]): next_ = container_of(pos.sibling.next, "struct cgroup_subsys_state", "sibling") else: serial_nr = pos.serial_nr.value_() # Read once and cache. for next_ in list_for_each_entry( "struct cgroup_subsys_state", parent.children.address_of_(), "sibling" ): if next_.serial_nr > serial_nr: break if next_.sibling.address_of_() != parent.children.address_of_(): return next_ return NULL(next_.prog_, "struct cgroup_subsys_state *")
def for_each_mount( prog_or_ns: Union[Program, Object], src: Optional[Path] = None, dst: Optional[Path] = None, fstype: Optional[Union[str, bytes]] = None, ) -> Iterator[Object]: """ Iterate over all of the mounts in a given namespace. :param prog_or_ns: ``struct mnt_namespace *`` to iterate over, or :class:`Program` to iterate over initial mount namespace. :param src: Only include mounts with this source device name. :param dst: Only include mounts with this destination path. :param fstype: Only include mounts with this filesystem type. :return: Iterator of ``struct mount *`` objects. """ if isinstance(prog_or_ns, Program): ns = prog_or_ns["init_task"].nsproxy.mnt_ns else: ns = prog_or_ns if src is not None: src = os.fsencode(src) if dst is not None: dst = os.fsencode(dst) if fstype: fstype = os.fsencode(fstype) for mnt in list_for_each_entry("struct mount", ns.list.address_of_(), "mnt_list"): if ((src is None or mount_src(mnt) == src) and (dst is None or mount_dst(mnt) == dst) and (fstype is None or mount_fstype(mnt) == fstype)): yield mnt
def for_each_mount(prog_or_ns, src=None, dst=None, fstype=None): """ .. c:function:: for_each_mount(struct mnt_namespace *ns, char *src, char *dst, char *fstype) Iterate over all of the mounts in a given namespace. If given a :class:`Program` instead, the initial mount namespace is used. returned mounts can be filtered by source, destination, or filesystem type, all of which are encoded using :func:`os.fsencode()`. :return: Iterator of ``struct mount *`` objects. """ if isinstance(prog_or_ns, Program): ns = prog_or_ns['init_task'].nsproxy.mnt_ns else: ns = prog_or_ns if src is not None: src = os.fsencode(src) if dst is not None: dst = os.fsencode(dst) if fstype: fstype = os.fsencode(fstype) for mnt in list_for_each_entry('struct mount', ns.list.address_of_(), 'mnt_list'): if ((src is None or mount_src(mnt) == src) and (dst is None or mount_dst(mnt) == dst) and (fstype is None or mount_fstype(mnt) == fstype)): yield mnt
def _for_each_block_device(prog): try: class_in_private = prog.cache['knode_class_in_device_private'] except KeyError: # We need a proper has_member(), but this is fine for now. class_in_private = any(member[1] == 'knode_class' for member in prog.type('struct device_private').members) prog.cache['knode_class_in_device_private'] = class_in_private devices = prog['block_class'].p.klist_devices.k_list.address_of_() if class_in_private: for device_private in list_for_each_entry('struct device_private', devices, 'knode_class.n_node'): yield device_private.device else: yield from list_for_each_entry('struct device', devices, 'knode_class.n_node')
def path_lookup(prog_or_root, path, allow_negative=False): """ .. c:function:: struct path path_lookup(struct path *root, const char *path, bool allow_negative) Look up the given path name relative to the given root directory. If given a :class:`Program` instead of a ``struct path``, the initial root filesystem is used. :param bool allow_negative: Whether to allow returning a negative dentry (i.e., a dentry for a non-existent path). :raises Exception: if the dentry is negative and ``allow_negative`` is ``False``, or if the path is not present in the dcache. The latter does not necessarily mean that the path does not exist; it may be uncached. On a live system, you can make the kernel cache the path by accessing it (e.g., with :func:`open()` or :func:`os.stat()`): >>> path_lookup(prog, '/usr/include/stdlib.h') ... Exception: could not find '/usr/include/stdlib.h' in dcache >>> open('/usr/include/stdlib.h').close() >>> path_lookup(prog, '/usr/include/stdlib.h') (struct path){ .mnt = (struct vfsmount *)0xffff8b70413cdca0, .dentry = (struct dentry *)0xffff8b702ac2c480, } """ if isinstance(prog_or_root, Program): prog_or_root = prog_or_root["init_task"].fs.root mnt = root_mnt = container_of(prog_or_root.mnt.read_(), "struct mount", "mnt") dentry = root_dentry = prog_or_root.dentry.read_() components = os.fsencode(path).split(b"/") for i, component in enumerate(components): if component == b"" or component == b".": continue elif component == b"..": mnt, dentry = _follow_dotdot(mnt, dentry, root_mnt, root_dentry) else: for child in list_for_each_entry("struct dentry", dentry.d_subdirs.address_of_(), "d_child"): if child.d_name.name.string_() == component: dentry = child break else: failed_path = os.fsdecode(b"/".join(components[:i + 1])) raise Exception(f"could not find {failed_path!r} in dcache") mnt, dentry = _follow_mount(mnt, dentry) if not allow_negative and not dentry.d_inode: failed_path = os.fsdecode(b"/".join(components)) raise Exception(f"{failed_path!r} dentry is negative") return Object( mnt.prog_, "struct path", value={ "mnt": mnt.mnt.address_of_(), "dentry": dentry, }, )
def for_each_slab_cache(prog: Program) -> Iterator[Object]: """ Iterate over all slab caches. :return: Iterator of ``struct kmem_cache *`` objects. """ return list_for_each_entry("struct kmem_cache", prog["slab_caches"].address_of_(), "list")
def _for_each_block_device(prog: Program) -> Iterator[Object]: try: class_in_private = prog.cache["knode_class_in_device_private"] except KeyError: # Linux kernel commit 570d0200123f ("driver core: move # device->knode_class to device_private") (in v5.1) moved the list # node. class_in_private = prog.type("struct device_private").has_member("knode_class") prog.cache["knode_class_in_device_private"] = class_in_private devices = prog["block_class"].p.klist_devices.k_list.address_of_() if class_in_private: for device_private in list_for_each_entry( "struct device_private", devices, "knode_class.n_node" ): yield device_private.device else: yield from list_for_each_entry("struct device", devices, "knode_class.n_node")
def _for_each_block_device(prog): try: class_in_private = prog.cache["knode_class_in_device_private"] except KeyError: # We need a proper has_member(), but this is fine for now. class_in_private = any( member.name == "knode_class" for member in prog.type("struct device_private").members) prog.cache["knode_class_in_device_private"] = class_in_private devices = prog["block_class"].p.klist_devices.k_list.address_of_() if class_in_private: for device_private in list_for_each_entry("struct device_private", devices, "knode_class.n_node"): yield device_private.device else: yield from list_for_each_entry("struct device", devices, "knode_class.n_node")
def for_each_net(prog: Program) -> Iterator[Object]: """ Iterate over all network namespaces in the system. :return: Iterator of ``struct net *`` objects. """ for net in list_for_each_entry("struct net", prog["net_namespace_list"].address_of_(), "list"): yield net
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 objs(cache: drgn.Object) -> int: assert cache.type_.type_name() == 'struct kmem_cache *' count = cache.node[0].total_objects.counter.value_() if not is_root_cache(cache): return count for child in list_for_each_entry("struct kmem_cache", cache.memcg_params.children.address_of_(), "memcg_params.children_node"): count += objs(child) return count
def nr_slabs(cache: drgn.Object) -> int: assert cache.type_.type_name() == 'struct kmem_cache *' nslabs = cache.node[0].nr_slabs.counter.value_() if not is_root_cache(cache): return nslabs for child in list_for_each_entry("struct kmem_cache", cache.memcg_params.children.address_of_(), "memcg_params.children_node"): nslabs += nr_slabs(child) return nslabs
def test_list_for_each_entry(self): self.assertEqual( list( list_for_each_entry("struct drgn_test_list_entry", self.empty, "node")), [], ) self.assertEqual( list( list_for_each_entry("struct drgn_test_list_entry", self.full, "node")), [self.entry(i) for i in range(self.num_entries)], ) self.assertEqual( list( list_for_each_entry("struct drgn_test_list_entry", self.singular, "node")), [self.singular_entry], )
def for_each_partition(prog): """ Iterate over all partitions in the system. :return: Iterator of ``struct hd_struct *`` objects. """ devices = prog['block_class'].p.klist_devices.k_list.address_of_() for device in list_for_each_entry('struct device', devices, 'knode_class.n_node'): yield container_of(device, 'struct hd_struct', '__dev')
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 *' 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 for_each_disk(prog): """ Iterate over all disks in the system. :return: Iterator of ``struct gendisk *`` objects. """ devices = prog['block_class'].p.klist_devices.k_list.address_of_() disk_type = prog['disk_type'].address_of_() for device in list_for_each_entry('struct device', devices, 'knode_class.n_node'): if device.type == disk_type: yield container_of(device, 'struct gendisk', 'part0.__dev')
def cgroup_bpf_prog_for_each(cgrp, bpf_attach_type): """ .. c:function:: cgroup_bpf_prog_for_each(struct cgroup *cgrp, int bpf_attach_type) Iterate over all cgroup bpf programs of the given attach type attached to the given cgroup. :return: Iterator of ``struct bpf_prog *`` objects. """ progs_head = cgrp.bpf.progs[bpf_attach_type] for pl in list_for_each_entry("struct bpf_prog_list", progs_head.address_of_(), "node"): yield pl.prog
def cgroup_bpf_prog_for_each(cgrp: Object, bpf_attach_type: IntegerLike) -> Iterator[Object]: """ Iterate over all cgroup BPF programs of the given attach type attached to the given cgroup. :param cgrp: ``struct cgroup *`` :param bpf_attach_type: ``enum bpf_attach_type`` :return: Iterator of ``struct bpf_prog *`` objects. """ progs_head = cgrp.bpf.progs[bpf_attach_type] for pl in list_for_each_entry("struct bpf_prog_list", progs_head.address_of_(), "node"): yield pl.prog
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 find_module_memory_segment(prog: drgn.Program, mod_name: str) -> Tuple[int, int]: """ Looks for the segment in memory where `mod_name` is loaded within `prog`. Returns: (<base_offset>, <size>) if `mod_name` is found. (-1, 0) otherwise. """ for mod in list_for_each_entry('struct module', prog['modules'].address_of_(), 'list'): if mod.name.string_().decode("utf-8") == mod_name: return (mod.core_layout.base.value_(), mod.core_layout.size.value_()) return (-1, 0)
def test_validate_list_for_each_entry(self): for head in (self.empty, self.full, self.singular): self.assertEqual( list( validate_list_for_each_entry("struct drgn_test_list_entry", head, "node")), list( list_for_each_entry("struct drgn_test_list_entry", head, "node")), ) self.assertRaises( ValidationError, collections.deque, validate_list_for_each_entry("struct drgn_test_list_entry", self.corrupted, "node"), 0, )
def walk(self, blkcg, q_id, parent_path): if not self.include_dying and \ not (blkcg.css.flags.value_() & prog['CSS_ONLINE'].value_()): return name = BlkgIterator.blkcg_name(blkcg) path = parent_path + '/' + name if parent_path else name blkg = drgn.Object(prog, 'struct blkcg_gq', address=radix_tree_lookup(blkcg.blkg_tree, q_id)) if not blkg.address_: return self.blkgs.append((path if path else '/', blkg)) for c in list_for_each_entry('struct blkcg', blkcg.css.children.address_of_(), 'css.sibling'): self.walk(c, q_id, path)
def qdisc_lookup(dev: Object, major: IntegerLike) -> Object: """ Get a Qdisc from a device and a major handle number. It is worth noting that conventionally handles are hexadecimal, e.g. ``10:`` in a ``tc`` command means major handle 0x10. :param dev: ``struct net_device *`` :param major: Qdisc major handle number. :return: ``struct Qdisc *`` (``NULL`` if not found) """ major = operator.index(major) << 16 roots = [dev.qdisc] if dev.ingress_queue: roots.append(dev.ingress_queue.qdisc_sleeping) # Since Linux kernel commit 59cc1f61f09c ("net: sched: convert qdisc linked # list to hashtable") (in v4.7), a device's child Qdiscs are maintained in # a hashtable in its struct net_device. Before that, they are maintained in # a linked list in their root Qdisc. use_hashtable = dev.prog_.type("struct net_device").has_member( "qdisc_hash") for root in roots: if root.handle == major: return root if use_hashtable: for head in root.dev_queue.dev.qdisc_hash: for qdisc in hlist_for_each_entry("struct Qdisc", head.address_of_(), "hash"): if qdisc.handle == major: return qdisc else: for qdisc in list_for_each_entry("struct Qdisc", root.list.address_of_(), "list"): if qdisc.handle == major: return qdisc return NULL(dev.prog_, "struct Qdisc *")
def list_for_each_spl_kmem_cache(prog: drgn.Program) -> Iterable[drgn.Object]: yield from list_for_each_entry("spl_kmem_cache_t", prog["spl_kmem_cache_list"].address_of_(), "skc_list")
# Copyright (c) Facebook, Inc. and its affiliates. # SPDX-License-Identifier: GPL-3.0+ """An implementation of lsmod(8) using drgn""" from drgn.helpers.linux.list import list_for_each_entry print("Module Size Used by") for mod in list_for_each_entry("struct module", prog["modules"].address_of_(), "list"): name = mod.name.string_().decode() size = (mod.init_layout.size + mod.core_layout.size).value_() refcnt = mod.refcnt.counter.value_() - 1 print(f"{name:19} {size:>8} {refcnt}", end="") first = True for use in list_for_each_entry("struct module_use", mod.source_list.address_of_(), "source_list"): if first: print(" ", end="") first = False else: print(",", end="") print(use.source.name.string_().decode(), end="") print()
def get_module_paths(osrelease: str, path: str) -> List[str]: """ Use drgn on the crash dump specified by `path` and return list of paths from the `osrelease` kernel modules relevant to the crash dump. """ # # Similarly to libkdumpfile we import these libraries locally # here so people who don't have drgn can still use savedump # for userland core dumps. # import drgn # pylint: disable=import-outside-toplevel from drgn.helpers.linux.list import list_for_each_entry # pylint: disable=import-outside-toplevel prog = drgn.program_from_core_dump(path) # # First go through all modules in the dump and create a map # of [key: module name] -> (value: module srcversion). # # Note: # It would be prefereable to be able to use the binary's # .build-id to do the matching instead of srcversion. # Unfortunately there doesn't seem to be a straightforward # way to get the build-id section of the ELF files recorded # in the dump. Hopefully that changes in the future. # mod_name_srcvers = {} for mod in list_for_each_entry('struct module', prog['modules'].address_of_(), 'list'): mod_name_srcvers[str(mod.name.string_(), encoding='utf-8')] = str(mod.srcversion.string_(), encoding='utf-8') # # Go through all modules in /usr/lib/debug/lib/modules/<osrelease> # and gather the file paths of the ones that are part of our # module name-to-srcversion map. # system_modules = pathlib.Path( f"/usr/lib/debug/lib/modules/{osrelease}/").rglob('*.ko') mod_paths = [] for modpath in system_modules: modname = os.path.basename(modpath)[:-3] if not mod_name_srcvers.get(modname): continue success, output = shell_cmd( ['modinfo', '--field=srcversion', str(modpath)]) if not success: sys.exit(output) output = output.strip() if output != mod_name_srcvers[modname]: continue mod_paths.append(str(modpath)) del mod_name_srcvers[modname] print(f"found {len(mod_paths)} relevant modules with their debug info...") print("warning: could not find the debug info of the following modules:") print(f" {', '.join(mod_name_srcvers.keys())}") return mod_paths
# Copyright (c) Facebook, Inc. and its affiliates. # SPDX-License-Identifier: GPL-3.0+ """List the paths of all inodes cached in a given filesystem""" from drgn.helpers.linux.fs import for_each_mount, inode_path from drgn.helpers.linux.list import list_for_each_entry import os import sys import time if len(sys.argv) == 1: path = "/" else: path = sys.argv[1] mnt = None for mnt in for_each_mount(prog, dst=path): pass if mnt is None: sys.exit(f"No filesystem mounted at {path}") sb = mnt.mnt.mnt_sb for inode in list_for_each_entry("struct inode", sb.s_inodes.address_of_(), "i_sb_list"): try: print(os.fsdecode(inode_path(inode))) except ValueError: continue