1# Copyright 2021 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Utilities for address symbolization.""" 15 16import abc 17from typing import Iterable 18from dataclasses import dataclass 19 20 21@dataclass(frozen=True) 22class Symbol: 23 """Symbols produced by a symbolizer.""" 24 25 address: int 26 name: str = '' 27 file: str = '' 28 line: int = 0 29 30 def to_string(self, max_filename_len: int = 0) -> str: 31 if not self.name: 32 name = f'0x{self.address:08X}' 33 else: 34 name = self.name 35 36 return f'{name} ({self.file_and_line(max_filename_len)})' 37 38 def file_and_line(self, max_filename_len: int = 0) -> str: 39 """Returns a file/line number string, with question marks if unknown.""" 40 41 if not self.file: 42 return '??:?' 43 44 if max_filename_len and len(self.file) > max_filename_len: 45 return f'[...]{self.file[-max_filename_len:]}:{self.line}' 46 47 return f'{self.file}:{self.line}' 48 49 def __str__(self): 50 return self.to_string() 51 52 53class Symbolizer(abc.ABC): 54 """An interface for symbolizing addresses.""" 55 56 @abc.abstractmethod 57 def symbolize(self, address: int) -> Symbol: 58 """Symbolizes an address using a loaded binary or symbol database.""" 59 60 def dump_stack_trace( 61 self, addresses, most_recent_first: bool = True 62 ) -> str: 63 """Symbolizes and dumps a list of addresses as a stack trace. 64 65 most_recent_first controls the hint provided at the top of the stack 66 trace. If call stack depth increases with each element in the input 67 list, most_recent_first should be false. 68 """ 69 order: str = 'first' if most_recent_first else 'last' 70 71 stack_trace: list[str] = [] 72 stack_trace.append(f'Stack Trace (most recent call {order}):') 73 74 max_width = len(str(len(addresses))) 75 for i, address in enumerate(addresses): 76 depth = i + 1 77 symbol = self.symbolize(address) 78 79 if symbol.name: 80 sym_desc = f'{symbol.name} (0x{symbol.address:08X})' 81 else: 82 sym_desc = f'(0x{symbol.address:08X})' 83 84 stack_trace.append(f' {depth:>{max_width}}: at {sym_desc}') 85 stack_trace.append(f' in {symbol.file_and_line()}') 86 87 return '\n'.join(stack_trace) 88 89 90class FakeSymbolizer(Symbolizer): 91 """A fake symbolizer that only knows a fixed set of symbols.""" 92 93 def __init__(self, known_symbols: Iterable[Symbol] | None = None): 94 if known_symbols is not None: 95 self._db = {sym.address: sym for sym in known_symbols} 96 else: 97 self._db = {} 98 99 def symbolize(self, address: int) -> Symbol: 100 """Symbolizes a fixed symbol database.""" 101 if address in self._db: 102 return self._db[address] 103 104 return Symbol(address) 105