Source code for crash.infra.callback
# -*- coding: utf-8 -*-
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
from typing import Any, Callable, List, Optional, TypeVar, Union
import abc
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(metaclass=abc.ABCMeta):
"""
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.
"""
_target_waitlist: List['ObjfileEventCallback'] = list()
_pending_list: List['ObjfileEventCallback'] = list()
_paused: bool = False
_connected_to_objfile_callback: bool = False
[docs]
def check_target(self) -> bool:
return isinstance(gdb.current_target(), gdb.LinuxKernelTarget)
def __init__(self, wait_for_target: bool = True) -> None:
self.completed = False
self.connected = False
self._waiting_for_target = wait_for_target and not self.check_target()
if not self._connected_to_objfile_callback:
# pylint: disable=no-member
gdb.events.new_objfile.connect(self._new_objfile_callback)
self._connected_to_objfile_callback = True
# pylint: disable=unused-argument
@classmethod
def _new_objfile_callback(cls, event: gdb.NewObjFileEvent) -> None:
cls.evaluate_all()
[docs]
@classmethod
def target_ready(cls) -> None:
for callback in cls._target_waitlist:
callback.complete_wait_for_target()
cls._target_waitlist[:] = list()
cls._update_pending()
[docs]
@classmethod
def evaluate_all(cls) -> None:
if not cls._paused:
for callback in cls._pending_list:
callback.evaluate(False)
cls._update_pending()
[docs]
@classmethod
def pause(cls) -> None:
cls._paused = True
[docs]
@classmethod
def unpause(cls) -> None:
cls._paused = False
cls.evaluate_all()
[docs]
@classmethod
def dump_lists(cls) -> None:
print(f"Pending list: {[str(x) for x in ObjfileEventCallback._pending_list]}")
print(f"Target waitlist: {[str(x) for x in ObjfileEventCallback._target_waitlist]}")
[docs]
def complete_wait_for_target(self) -> None:
self._waiting_for_target = False
self.evaluate(False)
[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
if not self._waiting_for_target:
# 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.
if gdb.objfiles():
self.evaluate()
else:
self._target_waitlist.append(self)
if self.completed is False:
self.connected = True
self._pending_list.append(self)
return self.completed
@classmethod
def _update_pending(cls) -> None:
cls._pending_list[:] = [x for x in cls._pending_list if x.connected]
[docs]
def complete(self, update_now: bool = True) -> None:
"""
Complete and disconnect this callback from the event system.
Raises:
:obj:`CallbackCompleted`: This callback has already been completed.
"""
if not self.completed:
self.completed = True
if self.connected:
self.connected = False
if update_now:
self._update_pending()
else:
raise CallbackCompleted(self)
[docs]
def evaluate(self, update_now: bool = True) -> None:
if not self._waiting_for_target:
try:
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(update_now)
except gdb.error:
pass
[docs]
@abc.abstractmethod
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`.
"""
pass
[docs]
@abc.abstractmethod
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.
"""
pass
[docs]
def target_ready() -> None:
ObjfileEventCallback.target_ready()
[docs]
def evaluate_all() -> None:
ObjfileEventCallback.evaluate_all()
[docs]
def pause_objfile_callbacks() -> None:
ObjfileEventCallback.pause()
[docs]
def unpause_objfile_callbacks() -> None:
ObjfileEventCallback.unpause()
[docs]
def dump_lists() -> None:
ObjfileEventCallback.dump_lists()