Пример #1
0
 def _call_one(self, obj: drgn.Object) -> Iterable[drgn.Object]:
     try:
         sum_ = drgn_percpu.percpu_counter_sum(obj)
     except AttributeError as err:
         raise sdb.CommandError(self.name,
                                "input is not a percpu_counter") from err
     yield drgn.Object(sdb.get_prog(), type="s64", value=sum_)
Пример #2
0
 def get_frame_pcs(task: drgn.Object) -> List[int]:
     frame_pcs = []
     try:
         for frame in sdb.get_prog().stack_trace(task):
             frame_pcs.append(frame.pc)
     except ValueError:
         #
         # Unwinding the stack of a running/runnable task will
         # result in an exception. Since we expect some tasks to
         # be running, we silently ignore this case, and move on.
         #
         # Unfortunately, the exception thrown in this case is a
         # generic "ValueError" exception, so we may wind up
         # masking other "ValueError" exceptions that are not due
         # to unwinding the stack of a running task.
         #
         # We can't check the state of the task here, and verify
         # it's in the "R" state, since that state can change in
         # between the point where the "ValueError" exception was
         # originally raised, and here where we'd verify the
         # state of the task; i.e. it could have concurrently
         # transitioned from running to some other state.
         #
         pass
     return frame_pcs
Пример #3
0
def get_valid_struct_name(cmd: sdb.Command, tname: str) -> str:
    """
    If tname is a name of a type that's a typedef to a
    struct, this function return will it as is. If this
    is a name of a struct, then it returns the canonical
    name (e.g. adds "struct" prefix). Otherwise, raises
    an error.

    Used for shorthands in providing names of structure
    types to be consumed by drgn interfaces in string
    form (linux_list, container_of, etc..).
    """
    if tname in ['struct', '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}' and try again")

    try:
        type_ = sdb.get_prog().type(tname)
    except LookupError:
        # Check for struct
        struct_name = f"struct {tname}"
        try:
            type_ = sdb.get_prog().type(struct_name)
        except LookupError as err:
            raise sdb.CommandError(cmd.name, str(err))
        return struct_name

    # Check for typedef to struct
    if type_.kind != drgn.TypeKind.TYPEDEF:
        raise sdb.CommandError(
            cmd.name, f"{tname} is not a struct nor a typedef to a struct")
    if sdb.type_canonicalize(type_).kind != drgn.TypeKind.STRUCT:
        raise sdb.CommandError(cmd.name,
                               f"{tname} is not a typedef to a struct")
    return tname
Пример #4
0
def lookup_cache_by_address(obj: drgn.Object) -> Optional[drgn.Object]:
    pfn = virt_to_pfn(sdb.get_prog(), obj)
    page = pfn_to_page(pfn)
    cache = page.slab_cache
    try:
        # read the name to force FaultError if any
        _ = cache.name.string_().decode('utf-8')
    except drgn.FaultError:
        return None
    return cache
Пример #5
0
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)
Пример #6
0
 def no_input(self) -> Iterable[drgn.Object]:
     yield from for_each_task(sdb.get_prog())
Пример #7
0
    def _call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
        # pylint: disable=too-many-locals
        # pylint: disable=too-many-branches
        # pylint: disable=too-many-statements

        #
        # As the exception explains the code that follows this statement
        # only works for linux kernel targets (crash dumps or live systems).
        # When support for userland is added we can factor the kernel code
        # that follows into its own function and switch to the correct
        # codepath depending on the target.
        #
        if not sdb.get_target_flags() & drgn.ProgramFlags.IS_LINUX_KERNEL:
            raise sdb.CommandError(self.name,
                                   "userland targets are not supported yet")
        self.validate_args(self.args)

        #
        # Resolve TSTATE shortcut and/or sanitize it to standard uppercase
        # notation if it exists.
        #
        if self.args.tstate:
            if self.args.tstate in Stacks.TASK_STATE_SHORTCUTS:
                self.args.tstate = Stacks.TASK_STATES[
                    Stacks.TASK_STATE_SHORTCUTS[self.args.tstate]]
            else:
                self.args.tstate = self.args.tstate.upper()

        mod_start, mod_end = -1, -1
        if self.args.module:
            mod_start, mod_size = Stacks.find_module_memory_segment(
                self.args.module)
            assert mod_start != -1
            mod_end = mod_start + mod_size

        header = "{:<18} {:<16s}".format("TASK_STRUCT", "STATE")
        if not self.args.all:
            header += " {:>6s}".format("COUNT")
        print(header)
        print("=" * 42)

        #
        # We inspect and group the tasks by recording their state and
        # stack frames once in the following loop. We do this because
        # on live systems state can change under us, thus running
        # something like sdb.get_prog().stack_trace(task) twice (once for
        # grouping and once for printing) could yield different stack
        # traces resulting into misleading output.
        #
        stack_aggr: Dict[Any, List[drgn.Object]] = defaultdict(list)
        for task in for_each_task(sdb.get_prog()):
            stack_key = [Stacks.task_struct_get_state(task)]
            try:
                for frame in sdb.get_prog().stack_trace(task):
                    stack_key.append(frame.pc)
            except ValueError:
                #
                # Unwinding the stack of a running/runnable task will
                # result in an exception. Since we expect some tasks to
                # be running, we silently ignore this case, and move on.
                #
                # Unfortunately, the exception thrown in this case is a
                # generic "ValueError" exception, so we may wind up
                # masking other "ValueError" exceptions that are not due
                # to unwinding the stack of a running task.
                #
                # We can't check the state of the task here, and verify
                # it's in the "R" state, since that state can change in
                # between the point where the "ValueError" exception was
                # originally raised, and here where we'd verify the
                # state of the task; i.e. it could have concurrently
                # transitioned from running to some other state.
                #
                pass

            stack_aggr[tuple(stack_key)].append(task)

        for stack_key, tasks in sorted(stack_aggr.items(),
                                       key=lambda x: len(x[1]),
                                       reverse=True):
            task_state = stack_key[0]
            if self.args.tstate and self.args.tstate != task_state:
                continue

            stacktrace_info = ""
            if self.args.all:
                for task in tasks:
                    stacktrace_info += "{:<18s} {:<16s}\n".format(
                        hex(task.value_()), task_state)
            else:
                stacktrace_info += "{:<18s} {:<16s} {:6d}\n".format(
                    hex(tasks[0].value_()), task_state, len(tasks))

            mod_match, func_match = False, False

            #
            # Note on the type-check being ignored:
            # The original `stack_key` type is a list where the first
            # element is a string and the rest of them are integers
            # but this is not easily expressed in mypy, thus we ignore
            # the assignment error below.
            #
            frame_pcs: List[int] = stack_key[1:]  #type: ignore[assignment]
            for frame_pc in frame_pcs:
                if mod_start != -1 and mod_start <= frame_pc < mod_end:
                    mod_match = True

                try:
                    sym = sdb.get_symbol(frame_pc)
                    func, offset = sym.name, frame_pc - sym.address
                    if self.args.function and self.args.function == func:
                        func_match = True
                except LookupError:
                    func, offset = hex(frame_pc), 0x0

                #
                # As a potential future item, we may want to print
                # the frame with the module where the pc/function
                # belongs to. For example:
                #     txg_sync_thread+0x15e [zfs]
                #
                stacktrace_info += "{:18s}{}+{}\n".format("", func, hex(offset))

            if mod_start != -1 and not mod_match:
                continue
            if self.args.function and not func_match:
                continue
            print(stacktrace_info)
        return []
Пример #8
0
def for_each_object_in_cache(cache: drgn.Object) -> Iterable[drgn.Object]:
    """
    Goes through each object in the SLUB cache supplied
    yielding them to the consumer.
    """
    assert sdb.type_canonical_name(cache.type_) == 'struct kmem_cache *'
    pg_slab_flag = 1 << sdb.get_prog().constant('PG_slab').value_()

    cache_children = {child.value_() for child in for_each_child_cache(cache)}
    #
    # We are caching the list of partial slabs here so later we can
    # decide whether we want to use the freelist to find which entries
    # are allocated (partial slab case) or just read all the entries
    # (full slab case). This is not great for live-systems where these
    # data can be changing very quickly under us but its the best that
    # we can do given that the Linux kernel doesn't keep track of its
    # full slabs explicitly except in cases where very heavy-weight
    # debugging config options are enabled.
    #
    cache_partials = {
        partial_slab.value_()
        for partial_slab in for_each_partial_slab_in_cache(cache)
    }

    #
    # For the same reason mentioned above, since the Linux kernel
    # doesn't keep track of its full slabs by default in a list
    # as it does for partial slabs, we have to go through all the
    # pages in memory to find those slabs.
    #
    for page in for_each_page(sdb.get_prog()):
        try:
            if not page.flags & pg_slab_flag:
                continue
        except drgn.FaultError:
            #
            # Not 100% sure why this happens but we've verified
            # in multiple cases that we've been able to detect
            # all the slabs/pages from all the caches and none
            # of them fall into these segments that give us
            # FaultErrors. One hypothesis of why these occur
            # is because the regions that we've tried to get
            # rid from where origininally all zeros and they
            # where subsequently filtered out. Another one is
            # that these regions are just not mapped to anything.
            # In any case this scenario hasn't brought up any
            # issues with the functionality that we are trying
            # to provide.
            #
            continue

        slab_cache = page.slab_cache
        if slab_cache.value_() == 0x0:
            continue
        if slab_cache != cache and slab_cache.value_() not in cache_children:
            continue

        free_set: Set[int] = set()
        if page.value_() in cache_partials:
            try:
                free_set = {
                    obj.value_()
                    for obj in for_each_freeobj_in_slab(cache, page)
                }
            except drgn.FaultError as err:
                #
                # It is common for freelists of partials slabs to be
                # updated while we are looking at them. Instead of
                # stopping right away or going though and reading
                # more invalid data we just skip to the next slab.
                #
                print(
                    f"inconsistent freelist address:{hex(err.address)} page:{hex(page)}"
                )
                continue
        addr = page_to_virt(page) + cache_red_left_padding(cache)
        obj_size = slab_cache.size
        num_objects = page.objects
        end = addr + (num_objects * obj_size)

        p = addr
        while p < end:
            if p.value_() not in free_set:
                yield p
            p += obj_size
Пример #9
0
 def __init__(self,
              args: Optional[List[str]] = None,
              name: str = "_") -> None:
     super().__init__(args, name)
     self.ncpus = len(
         list(drgn_cpumask.for_each_possible_cpu(sdb.get_prog())))
Пример #10
0
 def no_input(self) -> Iterable[drgn.Object]:
     self.validate_context()
     yield from filter(self.match_stack, for_each_task(sdb.get_prog()))
Пример #11
0
 def _call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
     for pid in self.args.pid:
         yield drgn.helpers.linux.pid.find_task(sdb.get_prog(), pid)