Source code for crash.infra.callback

# -*- coding: utf-8 -*-
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

from typing import Callable, Any, Union, TypeVar, Optional

import gdb

Callback = Callable[[Any], Union[bool, None]]

OECType = TypeVar('OECType', bound='ObjfileEventCallback')

[docs]class CallbackCompleted(RuntimeError): """The callback has already been completed and is no longer valid""" def __init__(self, callback_obj: 'ObjfileEventCallback') -> None: msg = "Callback has already completed." super().__init__(msg) self.callback_obj = callback_obj
[docs]class ObjfileEventCallback: """ A generic objfile callback class When GDB loads an objfile, it can perform callbacks. These callbacks are triggered for every objfile loaded. Once marked complete, the callback is removed so it doesn't trigger for future objfile loads. Derived classes need only implement the complete and check_ready methods. Consumers of this interface must also call :meth:`connect_callback` to connect the object to the callback infrastructure. """ def __init__(self) -> None: self.completed = False self.connected = False self._setup_symbol_cache_flush_callback()
[docs] def connect_callback(self) -> bool: """ Connect this callback to the event system. Raises: :obj:`CallbackCompleted`: This callback has already been completed. """ if self.completed: raise CallbackCompleted(self) if self.connected: return False self.connected = True # We don't want to do lookups immediately if we don't have # an objfile. It'll fail for any custom types but it can # also return builtin types that are eventually changed. objfiles = gdb.objfiles() if objfiles: result = self.check_ready() if not (result is None or result is False): completed = self.callback(result) if completed is None: completed = True self.completed = completed if self.completed is False: # pylint: disable=no-member gdb.events.new_objfile.connect(self._new_objfile_callback) return self.completed
[docs] def complete(self) -> None: """ Complete and disconnect this callback from the event system. Raises: :obj:`CallbackCompleted`: This callback has already been completed. """ if not self.completed: # pylint: disable=no-member gdb.events.new_objfile.disconnect(self._new_objfile_callback) self.completed = True self.connected = False else: raise CallbackCompleted(self)
_symbol_cache_flush_setup = False @classmethod def _setup_symbol_cache_flush_callback(cls) -> None: if not cls._symbol_cache_flush_setup: # pylint: disable=no-member gdb.events.new_objfile.connect(cls._flush_symbol_cache_callback) cls._symbol_cache_flush_setup = True # GDB does this itself, but Python is initialized ahead of the # symtab code. The symtab observer is behind the python observers # in the execution queue so the cache flush executes /after/ us. @classmethod # pylint: disable=unused-argument def _flush_symbol_cache_callback(cls, event: gdb.NewObjFileEvent) -> None: gdb.execute("maint flush-symbol-cache") # pylint: disable=unused-argument def _new_objfile_callback(self, event: gdb.NewObjFileEvent) -> None: # GDB purposely copies the event list prior to calling the callbacks # If we remove an event from another handler, it will still be sent if self.completed: return result = self.check_ready() if not (result is None or result is False): completed = self.callback(result) if completed is True or completed is None: self.complete()
[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) -> Optional[bool]: """ The callback that derived classes implement for handling the sucessful result of :meth:`check_ready`. 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. """ raise NotImplementedError("callback must be implemented by derived class.")