Source code for crash.util.symbols

# -*- coding: utf-8 -*-
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
"""
The crash.util.symbols module provides a mechanism to simply discover
and resolve symbols, types, minimal symbols, and values.

A typical use is declaring a DelayedCollection at the top of a module and
using the DelayedCollection within the classes and functions that are
a part of the module.

Each of the collections defined here are instantiated using a list of
names that each collection type will resolve into a type, a symbol, a minimal
symbol, etc.  The names will by available as dictionary keys and also as
attribute names.  In the latter case, the names will be resolved into
a form usable as an attribute name.  See :class:`.Types` for more information.
"""

from typing import Type, List, Tuple, Callable, Union, Dict, Any

from crash.infra.lookup import DelayedType, DelayedSymbol, DelayedSymval
from crash.infra.lookup import DelayedValue, DelayedMinimalSymbol
from crash.infra.lookup import DelayedMinimalSymval
from crash.infra.lookup import NamedCallback, TypeCallback
from crash.infra.lookup import SymbolCallback, MinimalSymbolCallback
from crash.exceptions import DelayedAttributeError

import gdb

CollectedValue = Union[gdb.Type, gdb.Value, gdb.Symbol, gdb.MinSymbol, Any]
Names = Union[List[str], str]

[docs]class DelayedCollection: """ A generic container for delayed lookups. In addition to the :meth:`get` method, the names are also accessible via attribute names (``__getattr__``) or dictionary keys (``__getitem__``). Args: cls: The type of :obj:`.DelayedValue` to be collected names: The names of all the symbols to be collected Attributes: attrs (:obj:`dict`): A dictionary that maps the attribute names to the :obj:`.DelayedValue` object associated with each one. While the ``__getattr__`` and ``__getitem__`` methods will return the contained object. This dictionary will contain the container object *or* the contained object if it has been overridden via :meth:`override`. """ def __init__(self, cls: Type[DelayedValue], names: Names) -> None: self.attrs: Dict[str, DelayedValue] = {} if isinstance(names, str): names = [names] for name in names: t = cls(name) self.attrs[t.attrname] = t self.attrs[t.name] = t
[docs] def get(self, name: str) -> CollectedValue: """ Obtain the object associated with name Args: name: The attribute name associated with the :obj:`.DelayedValue` Returns: :obj:`object`: The underlying object associated with this name. Raises: :obj:`NameError`: The name does not exist. :obj:`.DelayedAttributeError`: The name exists but the value has not been resolved yet. """ if name not in self.attrs: raise NameError(f"'{self.__class__}' object has no '{name}'") if self.attrs[name].value is not None: setattr(self, name, self.attrs[name].value) return self.attrs[name].value raise DelayedAttributeError(name)
[docs] def override(self, name: str, value: CollectedValue) -> None: """ Override the :obj:`.DelayedValue` stored in the collection At times it may be required to override the value kept in the collection. """ if not name in self.attrs: raise RuntimeError(f"{name} is not part of this collection") self.attrs[name].value = value
def __getitem__(self, name: str) -> Any: try: return self.get(name) except NameError as e: raise KeyError(str(e)) def __getattr__(self, name: str) -> Any: try: return self.get(name) except NameError as e: raise AttributeError(str(e))
[docs]class Types(DelayedCollection): """ A container to resolve :obj:`gdb.Type` objects from the symbol table as they become available. Example: .. code-block:: pycon >>> from crash.util.symbols import Types >>> types = Types(["struct foo", "struct foo *"]) >>> ex1 = types.foo_type >>> ex2 = types.foo_p_type >>> ex3 = types['foo_type'] >>> ex4 = types['struct foo'] See :meth:`~crash.infra.lookup.TypeCallback.resolve_type` for details. Args: names: A :obj:`str` or :obj:`list` of :obj:`str` containing the names of the types to resolve. """ def __init__(self, names: Names) -> None: super(Types, self).__init__(DelayedType, names)
[docs] def override(self, name: str, value: gdb.Type) -> None: # type: ignore """ Override the type value, resolving the type name first. The *real* type name is used, not the attribute name. .. code-block: pycon >>> t = gdb.lookup_type('struct foo') >>> types.override('struct foo', t) """ # pylint: disable=unused-variable (name, attrname, pointer) = TypeCallback.resolve_type(name) super().override(name, value) super().override(attrname, value)
[docs]class Symbols(DelayedCollection): """ A container to resolve :obj:`gdb.Symbol` objects from the symbol table as they become available. Example: .. code-block:: pycon >>> from crash.util.symbols import Symvals >>> symbols = Symbols(["modules", "super_blocks"]) >>> print(symbols.modules) modules >>> print(symbols['modules']) modules >>> print(symbols.modules.type) <class 'gdb.Symbol'> Args: names: A :obj:`str` or :obj:`list` of :obj:`str` containing the names of the symbols to resolve. """ def __init__(self, names: Names) -> None: super(Symbols, self).__init__(DelayedSymbol, names)
[docs]class Symvals(DelayedCollection): """ A container to resolve :obj:`gdb.Symbol` objects from the symbol table as they become available and use the associated values as the stored object. Example: .. code-block:: pycon >>> from crash.util.symbols import Symvals >>> symvals = Symvals(["modules", "super_blocks"]) >>> print(symvals.modules) { next = 0xffffffffc0675208 <__this_module+8>, prev = 0xffffffffc00e8b48 <__this_module+8> } >>> print(symvals.modules.address) 0xffffffffab0ff030 <modules> >>> print(symvals['modules']) { next = 0xffffffffc0675208 <__this_module+8>, prev = 0xffffffffc00e8b48 <__this_module+8> } >>> print(symvals.modules.type) <class 'gdb.Value'> Args: names: A :obj:`str` or :obj:`list` of :obj:`str` containing the names of the symbols to resolve. """ def __init__(self, names: Names) -> None: super(Symvals, self).__init__(DelayedSymval, names)
[docs]class MinimalSymbols(DelayedCollection): """ A container to resolve :obj:`gdb.MinSymbol` objects from the symbol table as they become available. Minimal symbols don't have any type information associated with them so they are mostly used to resolve names to addresses. Example: .. code-block:: pycon >>> import gdb >>> from crash.util.symbols import MinimalSymbols >>> msymbols = MinimalSymbols(['modules', 'super_block']) >>> print(msymbols.modules.type) 11 >>> print(gdb.MINSYMBOL_TYPE_FILE_BSS) 11 >>> print(msymbols['modules']) modules >>> print(msymbols['modules'].value()) <data variable, no debug info> >>> print(msymbols['modules'].value().address) 0xffffffff820ff030 <modules> >>> print(type(msymbols['modules'])) <class 'gdb.MinSymbol'> Args: names: A :obj:`str` or :obj:`list` of :obj:`str` containing the names of the minimal symbols to resolve. """ def __init__(self, names: Names) -> None: super(MinimalSymbols, self).__init__(DelayedMinimalSymbol, names)
[docs]class MinimalSymvals(DelayedCollection): """ A container to resolve :obj:`gdb.MinSymbol` objects from the symbol table as they become available and uses the address of the values associated with them as the stored object. Minimal symbols don't have any type information associated with them so they are mostly used to resolve names to addresses. Example: .. code-block:: pycon >>> import gdb from crash.util.symbols import MinimalSymvals >>> msymvals = MinimalSymvals(['modules', 'super_block']) >>> print(f"{msymvals.modules:#x}") 0xffffffff820ff030 >>> print(f"{msymvals['modules']:#x}") 0xffffffff820ff030 >>> print(type(msymvals['modules'])) <class 'int'> Args: names: A :obj:`str` or :obj:`list` of :obj:`str` containing the names of the minimal symbols to resolve. """ def __init__(self, names: Names) -> None: super(MinimalSymvals, self).__init__(DelayedMinimalSymval, names)
[docs]class DelayedValues(DelayedCollection): """ A container to keep generic :class:`.DelayedValue` objects. These will raise :obj:`.DelayedAttributeError` until :meth:`.DelayedValue.callback` is called with a value to populate it. The callback must be accessed via :attr:`.DelayedCollection.attrs` or the :obj:`.DelayedValue` object will be evaluated first, also raising :obj:`.DelayedAttributeError`. Example: .. code-block:: pycon >>> from crash.util.symbols import DelayedValues >>> dvals = DelayedValues(['generic_value', 'another_value']) >>> dvals.attrs['generic_value'].callback(True) >>> print(dvals.generic_value) True >>> print(dvals.another_value) Traceback (most recent call last): File "<string>", line 4, in <module> File "./build/lib/crash/util/symbols.py", line 107, in __getattr__ return self.get(name) File "./build/lib/crash/util/symbols.py", line 85, in get raise DelayedAttributeError(name) crash.exceptions.DelayedAttributeError: Delayed attribute another_value has not been completed. Args: names: The names to use for the :obj:`.DelayedValue` objects. """ def __init__(self, names: Names) -> None: super(DelayedValues, self).__init__(DelayedValue, names)
CallbackSpecifier = Tuple[str, Callable] CallbackSpecifiers = Union[List[CallbackSpecifier], CallbackSpecifier]
[docs]class CallbackCollection: def __init__(self, cls: Type[NamedCallback], cbs: CallbackSpecifiers) -> None: if isinstance(cbs, tuple): cbs = [cbs] for cb in cbs: t = cls(cb[0], cb[1]) setattr(self, t.attrname, t)
[docs]class TypeCallbacks(CallbackCollection): def __init__(self, cbs: CallbackSpecifiers) -> None: super().__init__(TypeCallback, cbs)
[docs]class SymbolCallbacks(CallbackCollection): def __init__(self, cbs: CallbackSpecifiers) -> None: super().__init__(SymbolCallback, cbs)
[docs]class MinimalSymbolCallbacks(CallbackCollection): def __init__(self, cbs: CallbackSpecifiers) -> None: super().__init__(MinimalSymbolCallback, cbs)