############################################################################ # tools/gdb/stack.py # # SPDX-License-Identifier: Apache-2.0 # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. The # ASF licenses this file to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance with the # License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # ############################################################################ import traceback import gdb import utils STACK_COLORATION_PATTERN = utils.get_symbol_value("STACK_COLOR") class Stack(object): def __init__(self, name, entry, base, alloc, size, cursp, align): # We don't care about the stack growth here, base always point to the lower address! self._thread_name = name self._thread_entry = entry self._stack_base = base self._stack_alloc = alloc self._stack_top = base + size self._cur_sp = cursp self._stack_size = size self._align = align self._pattern = STACK_COLORATION_PATTERN self._sanity_check() def _sanity_check(self): # do some basic sanity checking to make sure we have a sane stack object if self._stack_base < self._stack_alloc or not self._stack_size: raise gdb.GdbError("Inconsistant stack size...Maybe memory corruption?") # TODO: check if stack ptr is located at a sane address range! def cur_usage(self): usage = self._stack_top - self._cur_sp if self.is_stackof(): gdb.write("An overflow detected, dumping the stack:\n") ptr_4bytes = gdb.Value(self._stack_base).cast( gdb.lookup_type("unsigned int").pointer() ) for i in range(0, self._stack_size // 4): if i % 8 == 0: gdb.write(f"{hex(self._stack_base + 4 * i)}: ") gdb.write(f"{hex(ptr_4bytes[i]):10} ") if i % 8 == 7: gdb.write("\n") gdb.write("\n") raise gdb.GdbError( "pls check your stack size! @ {0} sp:{1:x} base:{2:x}".format( self._thread_name, self._cur_sp, self._stack_base ) ) return usage def check_max_usage(self): ptr_4bytes = gdb.Value(self._stack_base).cast( gdb.lookup_type("unsigned int").pointer() ) spare = 0 for i in range(0, self._stack_size // 4): if int(ptr_4bytes[i]) != self._pattern: spare = i * 4 break return self._stack_size - spare def max_usage(self): if not utils.get_symbol_value("CONFIG_STACK_COLORATION"): return 0 return self.check_max_usage() def avalaible(self): cur_usage = self.cur_usage() return self._stack_size - cur_usage def maxdepth_backtrace(self): raise gdb.GdbError("Not implemented yet", traceback.print_stack()) def cur_sp(self): return self._cur_sp def is_stackof(self): # we should notify the user if the stack overflow is about to happen as well! return self._cur_sp <= self._stack_base def has_stackof(self): max_usage = self.max_usage() return max_usage >= self._stack_size # Always refetch the stack infos, never cached as we may have threads created/destroyed # dynamically! def fetch_stacks(): stacks = dict() for tcb in utils.get_tcbs(): if ( tcb["task_state"] == gdb.parse_and_eval("TSTATE_TASK_RUNNING") and not utils.in_interrupt_context() ): sp = utils.get_sp() else: sp = utils.get_sp(tcb=tcb) try: stacks[int(tcb["pid"])] = Stack( tcb["name"].string(), hex(tcb["entry"]["pthread"]), # should use main? int(tcb["stack_base_ptr"]), int(tcb["stack_alloc_ptr"]), int(tcb["adj_stack_size"]), sp, 4, ) except gdb.GdbError as e: gdb.write(f"Failed to construction stack object for tcb: {e}") return stacks class StackUsage(gdb.Command): """Display the stack usage of each thread, similar to cat /proc//stack""" def __init__(self): super(StackUsage, self).__init__("stack-usage", gdb.COMMAND_USER) self._stacks = [] # format template self._fmt = ( "{0: <4} | {1: <10} | {2: <10} | {3: <20} | {4: <10} | {5: <10} | {6: <10}" ) def format_print(self, pid, stack): def gen_info_str(x): usage = x / stack._stack_size res = ",".join([str(x), "{0:.2%}".format(usage)]) if usage > 0.8: res += "!" return res gdb.write( self._fmt.format( pid, stack._thread_name[:10], stack._thread_entry, hex(stack._stack_base), stack._stack_size, gen_info_str(stack.cur_usage()), gen_info_str(stack.max_usage()), ) ) gdb.write("\n") def invoke(self, args, from_tty): stacks = fetch_stacks() args = [int(arg) for arg in args.split()] pids = stacks.keys() if len(args) == 0 else args gdb.write( self._fmt.format( "Pid", "Name", "Entry", "Base", "Size", "CurUsage", "MaxUsage" ) ) gdb.write("\n") for pid in pids: stack = stacks.get(pid) if not stack: continue self.format_print(pid, stack) StackUsage()