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