# -*- coding: utf-8 -*-
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
from typing import Tuple, Any, Union, Optional
from crash.infra.callback import ObjfileEventCallback
from crash.infra.callback import Callback
from crash.exceptions import DelayedAttributeError
import gdb
[docs]class NamedCallback(ObjfileEventCallback):
"""
A base class for Callbacks with names
This cannot be used directly since it does not provide a
method for :meth:`.ObjfileEventCallback.callback`.
Args:
name: The name of the symbol or type to be resolved.
callback: A function to call with the result of the derived class's
:meth:`.ObjfileEventCallback.check_ready` method.
attrname (optional): A name safe for use as an attribute name.
If unspecified, defaults to the same string as name.
Attributes:
name (:obj:`str`): The name of the symbol or type being resolved.
attrname (:obj:`str`): The name of symbol or type being resolved
translated for use as an attribute name.
"""
def __init__(self, name: str, callback: Callback,
attrname: str = None) -> None:
super().__init__()
self.name = name
self.attrname = self.name
if attrname is not None:
self.attrname = attrname
self._callback = callback
# This is silly but it avoids pylint abstract-method warnings
[docs] def check_ready(self) -> Any:
"""
The method that derived classes implement for detecting when the
conditions required to call the callback have been met.
Returns:
:obj:`object`: This method can return an arbitrary object. It will
be passed untouched to :meth:`callback` if the result is anything
other than :obj:`None` or :obj:`False`.
"""
raise NotImplementedError("check_ready must be implemented by derived class.")
[docs] def callback(self, result: Any) -> Union[None, bool]:
"""
The callback for handling the sucessful result of :meth:`check_ready`.
It indirectly calls the callback specified in the constructor.
Args:
result: The result returned from :meth:`check_ready`
Returns:
:obj:`None` or :obj:`bool`: If :obj:`None` or :obj:`True`,
the callback succeeded and will be completed and removed.
Otherwise, the callback will stay connected for future completion.
"""
return self._callback(result)
[docs]class MinimalSymbolCallback(NamedCallback):
"""
A callback that executes when the named minimal symbol is
discovered in the objfile and returns the :obj:`gdb.MinSymbol`.
The callback must accept a :obj:`gdb.MinSymbol` and return
:obj:`bool` or :obj:`None`.
Args:
name: The name of the minimal symbol to discover
callback: The callback to execute when the minimal symbol is discovered
symbol_file (optional): Name of the symbol file to use
"""
def __init__(self, name: str, callback: Callback,
symbol_file: str = None) -> None:
super().__init__(name, callback)
self.symbol_file = symbol_file
self.connect_callback()
[docs] def check_ready(self) -> Optional[gdb.MinSymbol]:
"""
Returns the result of looking up the minimal symbol when a new
object file is loaded.
Returns:
:obj:`gdb.MinSymbol`: The requested minimal symbol
"""
return gdb.lookup_minimal_symbol(self.name, self.symbol_file, None)
def __str__(self) -> str:
return ("<{}({}, {}, {})>"
.format(self.__class__.__name__, self.name,
self.symbol_file, self.callback))
[docs]class SymbolCallback(NamedCallback):
"""
A callback that executes when the named symbol is discovered in the
objfile and returns the :obj:`gdb.Symbol`.
The callback must accept a :obj:`gdb.Symbol` and return :obj:`bool`
or :obj:`None`.
Args:
name: The name of the symbol to discover
callback: The callback to execute when the symbol is discovered
domain (optional): The domain to search for the symbol. The value
is assumed to be one of the value associated with :obj:`gdb.Symbol`
constant, i.e. SYMBOL_*_DOMAIN.
"""
def __init__(self, name: str, callback: Callback,
domain: int = gdb.SYMBOL_VAR_DOMAIN) -> None:
super().__init__(name, callback)
self.domain = domain
self.connect_callback()
[docs] def check_ready(self) -> Optional[gdb.Symbol]:
"""
Returns the result of looking up the symbol when a new object
file is loaded.
Returns:
:obj:`gdb.Symbol`: The requested symbol
"""
return gdb.lookup_symbol(self.name, None, self.domain)[0]
def __str__(self) -> str:
return ("<{}({}, {})>"
.format(self.__class__.__name__, self.name, self.domain))
[docs]class SymvalCallback(SymbolCallback):
"""
A callback that executes when the named symbol is discovered in the
objfile and returns the :obj:`gdb.Value` associated with the
:obj:`gdb.Symbol`.
The callback must accept a :obj:`gdb.Value` and return :obj:`bool`
or :obj:`None`.
See :obj:`SymbolCallback` for arguments.
"""
[docs] def check_ready(self) -> Optional[gdb.Value]: # type: ignore
"""
After successfully looking up the :obj:`gdb.Symbol`, returns
the :obj:`gdb.Value` associated with it.
Returns:
:obj:`gdb.Value`: The value associated with the requested symbol
"""
sym = super().check_ready()
if sym is not None:
try:
return sym.value()
except gdb.MemoryError:
pass
return None
[docs]class TypeCallback(NamedCallback):
"""
A callback that executes when the named type is discovered in the
objfile and returns the :obj:`gdb.Type` associated with it.
The callback must accept a :obj:`gdb.Type` and return :obj:`bool`
or :obj:`None`.
Args:
name: The name of the type to discover
callback: The callback to execute when the type is discovered
block (optional): The :obj:`gdb.Block` to search for the symbol
"""
def __init__(self, name: str, callback: Callback,
block: gdb.Block = None) -> None:
(name, attrname, self.pointer) = self.resolve_type(name)
super().__init__(name, callback, attrname)
self.block = block
self.connect_callback()
[docs] @staticmethod
def resolve_type(name: str) -> Tuple[str, str, bool]:
"""
This function takes a C type name and translates it into a 3-tuple
that contains the basic type name, the type name translated to
a form suitable for an attribute name, and whether the type
corresponds to a pointer.
The basic type name has all leading and trailing whitespace stripped,
and any ``*`` removed.
The attribute type name takes that base, removes the leading
``struct`` for structure types, removes any leading or trailing
whitespace, replaces internal spaces with underscores, and appends
a ``_type`` or ``_p_type`` suffix, depending on whether the type
is a pointer type.
Some examples:
- ``struct foo`` → ``foo_type``
- ``struct foo *`` → ``foo_p_type``
- ``unsigned long`` → ``unsigned_long_type``
*Notes*:
- Multiple levels of pointers are not handled properly. In
practice this means that ``struct foo *`` and
``struct foo **`` can't be used simultaneously. This is
typically not a problem.
- Unions are not handled as a special case as structs are. A
union type would use an attribute name of ``union_foo_type``.
Returns:
(:obj:`str`, :obj:`str`, :obj:`bool`): A 3-tuple consisting of
the basic type name, the name formatted for use as an attribute
name, and whether the type is a pointer type.
"""
pointer = False
name = name.strip()
if name[-1] == '*':
pointer = True
name = name[:-1].strip()
attrname = name
if name.startswith('struct '):
attrname = name[7:].strip()
if pointer:
attrname += '_p_type'
else:
attrname += '_type'
name = name
attrname = attrname.replace(' ', '_')
return (name, attrname, pointer)
[docs] def check_ready(self) -> Optional[gdb.Type]:
try:
return gdb.lookup_type(self.name, self.block)
except gdb.error:
return None
def __str__(self) -> str:
return ("<{}({}, {})>"
.format(self.__class__.__name__, self.name, self.block))
[docs]class DelayedValue:
"""
A generic class for making class attributes available that describe
to-be-loaded symbols, minimal symbols, and types.
"""
def __init__(self, name: str, attrname: str = None) -> None:
if name is None or not isinstance(name, str):
raise ValueError("Name must be a valid string")
self.name = name
if attrname is None:
self.attrname = name
else:
self.attrname = attrname
assert self.attrname is not None
self.value: Any = None
[docs] def get(self) -> Any:
if self.value is None:
raise DelayedAttributeError(self.name)
return self.value
[docs] def callback(self, value: Any) -> None:
if self.value is not None:
return
self.value = value
[docs]class DelayedMinimalSymbol(DelayedValue):
"""
A DelayedValue that handles minimal symbols.
Args:
name: The name of the minimal symbol
"""
def __init__(self, name: str) -> None:
super().__init__(name)
self.cb = MinimalSymbolCallback(name, self.callback)
def __str__(self) -> str:
return "{} attached with {}".format(self.__class__, str(self.cb))
[docs]class DelayedSymbol(DelayedValue):
"""
A DelayedValue that handles symbols.
Args:
name: The name of the symbol
"""
def __init__(self, name: str) -> None:
super().__init__(name)
self.cb = SymbolCallback(name, self.callback)
def __str__(self) -> str:
return "{} attached with {}".format(self.__class__, str(self.cb))
[docs]class DelayedType(DelayedValue):
"""
A DelayedValue for types.
Args:
name: The name of the type.
"""
def __init__(self, name: str) -> None:
(name, attrname, self.pointer) = TypeCallback.resolve_type(name)
super().__init__(name, attrname)
self.cb = TypeCallback(name, self.callback)
def __str__(self) -> str:
return "{} attached with {}".format(self.__class__, str(self.callback))
[docs] def callback(self, value: gdb.Type) -> None:
if self.pointer:
value = value.pointer()
self.value = value
[docs]class DelayedSymval(DelayedSymbol):
"""
A :obj:`DelayedSymbol` that returns the :obj:`gdb.Value`
associated with the symbol.
Args:
name: The name of the symbol.
"""
[docs] def callback(self, value: gdb.Symbol) -> None:
symval = value.value()
if symval.type.code == gdb.TYPE_CODE_FUNC:
symval = symval.address
self.value = symval
def __str__(self) -> str:
return "{} attached with {}".format(self.__class__, str(self.cb))
[docs]class DelayedMinimalSymval(DelayedMinimalSymbol):
"""
A DelayedMinimalSymbol that returns the address of the
minimal symbol as an :obj:`int`.
Args:
name: The name of the minimal symbol.
"""
[docs] def callback(self, value: gdb.MinSymbol) -> None:
self.value = int(value.value().address)
def __str__(self) -> str:
return "{} attached with {}".format(self.__class__, str(self.cb))