import argparse
import re
import shlex
from typing import List, Union
import gdb
from . import autocompeletion, utils
CONFIG_LIBC_BACKTRACE_DEPTH = utils.get_field_nitems(
"struct backtrace_entry_s", "stack"
)
class Backtrace:
"""
Convert addresses to backtrace
Usage:
backtrace = Backtrace(addresses=[0x4001, 0x4002, 0x4003])
# Access converted backtrace
addr, func, file, line = backtrace[0]
remaining = backtrace[1:] # Return list of (addr, func, file, line)
# Iterate over backtrace
for addr, func, file, line in backtrace:
print(addr, func, file, line)
# Append more addresses to convert
backtrace.append(0x40001234)
# Print backtrace
print(str(backtrace))
# Format backtrace to string
print("\n".join(backtrace.formatted))
# Custom formatter
backtrace = Backtrace(addresses=[0x4001, 0x4002, 0x4003], formatter="{:<6} {:<20} {}")
"""
def __init__(
self,
address: List[Union[gdb.Value, int]] = [],
formatter="{:<5} {:<36} {}\n",
break_null=True,
):
self.formatter = formatter
self._formatted = None
self.backtrace = []
for addr in address:
if break_null and not addr:
break
self.append(int(addr))
def __eq__(self, value: "Backtrace") -> bool:
return self.backtrace == value.backtrace
def __hash__(self) -> int:
return hash(tuple(self.backtrace))
def append(self, addr: Union[gdb.Value, int]) -> None:
"""Append an address to the backtrace"""
if result := utils.Symbol(addr):
self.backtrace.append(
(result.address, result.func, result.filename, result.line)
)
self._formatted = None
@property
def formatted(self):
"""Return the formatted backtrace string list"""
if not self._formatted:
self._formatted = [
self.formatter.format(hex(addr), func, f"{file}:{line}")
for addr, func, file, line in self.backtrace
]
return self._formatted
def __repr__(self) -> str:
return f"Backtrace: {len(self.backtrace)} items"
def __str__(self) -> str:
return "".join(self.formatted)
def __iter__(self):
for item in self.backtrace:
yield item
def __getitem__(self, index):
return self.backtrace.__getitem__(index)
def toJSON(self):
return [
{"address": addr, "function": func, "source": source, "line": line}
for addr, func, source, line in self.backtrace
]
class BacktraceEntry:
backtrace_entry_s = utils.lookup_type("struct backtrace_entry_s")
def __init__(self, entry):
if int(entry) == 0:
self.entry = None
else:
self.entry = utils.Value(entry).cast(self.backtrace_entry_s.pointer())
def get(self):
if not self.entry:
return list()
stack = utils.ArrayIterator(self.entry.stack, self.entry.depth)
return [int(addr) for addr in stack]
def format(self, formatter: str = "{:<5} {:<36} {}\n"):
if not self.entry:
return ""
stack = utils.ArrayIterator(self.entry.stack, self.entry.depth)
return str(Backtrace(stack))
@autocompeletion.complete
class Addr2Line(gdb.Command):
"""Convert addresses or expressions
Usage: addr2line address1 address2 expression1
Example: addr2line 0x1234 0x5678
addr2line "0x1234 + pointer->abc" &var var->field function_name var
addr2line $pc $r1 "$r2 + var"
addr2line [24/08/29 20:51:02] [CPU1] [209] [ap] sched_dumpstack: backtrace| 0: 0x402cd484 0x4028357e
addr2line -f crash.log
addr2line -f crash.log -p 123
"""
formatter = "{:<20} {:<32} {}\n"
def get_argparser(self):
parser = argparse.ArgumentParser(
description="Convert addresses or expressions to source code location"
)
parser.add_argument(
"-f", "--file", type=str, metavar="file", help="Crash log to analyze."
)
parser.add_argument(
"-p",
"--pid",
type=int,
help="Only dump specified task backtrace from crash file.",
)
return parser
def __init__(self):
super().__init__("addr2line", gdb.COMMAND_USER)
self.parser = self.get_argparser()
def print_backtrace(self, addresses, pid=None):
if pid:
gdb.write(f"\nBacktrace of {pid}\n")
backtraces = Backtrace(addresses, formatter=self.formatter, break_null=False)
gdb.write(str(backtraces))
@utils.dont_repeat_decorator
def invoke(self, args, from_tty):
if not args:
gdb.write(Addr2Line.__doc__ + "\n")
return
pargs = None
try:
pargs, _ = self.parser.parse_known_args(gdb.string_to_argv(args))
except SystemExit:
pass
gdb.write(self.formatter.format("Address", "Symbol", "Source"))
if pargs and pargs.file:
pattern = re.compile(
r".*sched_dumpstack: backtrace\|\s*(\d+)\s*:\s*((?:(0x)?[0-9a-fA-F]+\s*)+)"
)
addresses = {}
with open(pargs.file, "r") as f:
for line in f:
match = pattern.match(line)
if not match:
continue
pid = match.group(1)
if pargs.pid is not None and pargs.pid != int(pid):
continue
addresses.setdefault(pid, [])
addresses[pid].extend(
[int(addr, 16) for addr in match.group(2).split()]
)
for pid, addr in addresses.items():
self.print_backtrace(addr, pid)
else:
addresses = []
for arg in shlex.split(args.replace(",", " ")):
if utils.is_decimal(arg):
addresses.append(int(arg))
elif utils.is_hexadecimal(arg):
addresses.append(int(arg, 16))
else:
try:
var = utils.parse_and_eval(f"{arg}")
addresses.append(var)
except gdb.error as e:
gdb.write(f"Ignore {arg}: {e}\n")
self.print_backtrace(addresses)
@autocompeletion.complete
class BacktracePool(gdb.Command):
"""Display the global backtrace information"""
def get_argparser(self):
parser = argparse.ArgumentParser(description=self.__doc__)
parser.add_argument("-d", "--detail", action="store_true")
parser.add_argument("-t", "--top", type=int, help="Display the top N backtrace")
return parser
def __init__(self):
super().__init__("backtracepool", gdb.COMMAND_USER)
self.parser = self.get_argparser()
def invoke(self, args, from_tty):
g_backtrace_pool = utils.parse_and_eval("g_backtrace_pool")
args = self.parser.parse_args(gdb.string_to_argv(args))
formatter = "{:>8} {:>8} {:>10} {:}\n"
btformat = formatter.format("", "", "", "")[:-1] + "{1:<48}{2}\n"
gdb.write(formatter.format("slot", "depth", "refcount", "stack"))
backtrace_pool = []
for i in range(g_backtrace_pool.capacity):
if not g_backtrace_pool.bucket[i]:
continue
entry = BacktraceEntry(g_backtrace_pool.bucket[i])
entry.index = i
backtrace_pool.append(entry)
backtrace_pool.sort(key=lambda x: x.entry.ref, reverse=True)
if args.top:
backtrace_pool = backtrace_pool[: args.top]
for entry in backtrace_pool:
stack = ""
if args.detail:
stack = f"\n{entry.format(btformat)}"
else:
stack = " ".join(hex(addr) for addr in entry.get())
gdb.write(
formatter.format(entry.index, entry.entry.depth, entry.entry.ref, stack)
)
gdb.write(
f"capacity: {g_backtrace_pool.capacity}, used: {g_backtrace_pool.used}\n"
)