Esempio n. 1
0
    def print_stacks(self, objs: Iterable[drgn.Object]) -> None:
        self.print_header()
        for stack_key, tasks in Stacks.aggregate_stacks(objs):
            stacktrace_info = ""
            task_state = stack_key[0]

            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))

            frame_pcs: Tuple[int, ...] = stack_key[1]
            for frame_pc in frame_pcs:
                try:
                    sym = sdb.get_symbol(frame_pc)
                    func = sym.name
                    offset = frame_pc - sym.address
                except LookupError:
                    func = hex(frame_pc)
                    offset = 0x0
                stacktrace_info += "{:18s}{}+{}\n".format(
                    "", func, hex(offset))
            print(stacktrace_info)
Esempio n. 2
0
    def validate_args(self) -> None:
        if self.args.function:
            try:
                #
                # It would be simpler to resolve the symbol from the function
                # name directly but we use the address due to osandov/drgn#47.
                #
                func = sdb.get_object(self.args.function)
                sym = sdb.get_symbol(func.address_of_())
            except KeyError as err:
                raise sdb.CommandError(
                    self.name,
                    f"symbol '{self.args.function}' does not exist") from err
            if func.type_.kind != drgn.TypeKind.FUNCTION:
                raise sdb.CommandError(
                    self.name, f"'{self.args.function}' is not a function")
            self.func_start = sym.address
            self.func_end = self.func_start + sym.size

        if self.args.tstate:
            self.match_state = Stacks.resolve_state(self.args.tstate)
            task_states = Stacks.TASK_STATES.values()
            if self.match_state not in task_states:
                valid_states = ", ".join(task_states)
                raise sdb.CommandError(
                    self.name,
                    f"'{self.args.tstate}' is not a valid task state"
                    f" (acceptable states: {valid_states})")

        if self.args.module:
            if Stacks.find_module_memory_segment(self.args.module)[0] == -1:
                raise sdb.CommandError(
                    self.name,
                    f"module '{self.args.module}' doesn't exist or isn't currently loaded"
                )
            self.mod_start, mod_size = Stacks.find_module_memory_segment(
                self.args.module)
            assert self.mod_start != -1
            self.mod_end = self.mod_start + mod_size
Esempio n. 3
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 []