"""
`Link`, `Endpoint` and `CommEntity` provide the generic basis for
building the debugger representation of task-based program structure.
"""
import gdb
import mcgdb
from mcgdb.toolbox import my_gdb
###########################################################"
@my_gdb.Listed
@my_gdb.Numbered
[docs]class Link:
"""
Generic link between entity endpoints.
:param endpoint: (optional) One of the endpoints connected to this link.
:type endpoint: Endpoint
:var endpoints: the list of endpoints associated with this link.
"""
def __init__(self, endpoint=None):
self.endpoints = []
if endpoint is not None:
assert isinstance(endpoint, Endpoint)
self.endpoints.append(endpoint)
endpoint.link = self
assert endpoint in self.endpoints
[docs] def add_endpoint(self, endpoint):
"""
Connect another endpoint to this link.
:param endpoint: Endpoint to connect to the link.
"""
assert isinstance(endpoint, Endpoint)
endpoint.link = self
self.endpoints.append(endpoint)
[docs] def endpoint_closed(self, closed_endpoint):
"""
Inform the `Link` that one if its endpoints was closed.
The link will destruct itself when no more endpoints are connected to it.
param: closed_endpoint: endpoint that was closed.
"""
try:
self.endpoints.remove(closed_endpoint)
except ValueError:
pass
#check if the link persists after the closure
for endpoint in self.endpoints:
if endpoint.comm_entity.is_alive():
break
else:
self.destruct()
[docs] def destruct(self):
"""
Destructs this link and close the associated endpoints.
"""
while len(self.endpoints) != 0:
try:
self.endpoints.pop().endpoint_closed()
except ValueError:
pass
@my_gdb.virtual
[docs] def get_channel(self):
return None
[docs] def match_channel(self, channel):
"""
Tells if `channel` is identical with this link's.
: param channel: The channel to compare with this link's.
:returns: True if the channel if this link is the same as `channel`.
"""
return self.get_channel() is channel
[docs] def get_other_endpoints(self, endpoint):
"""
Returns the list of the _other_ endpoints connected to this link, if any.
:param endpoint: One end of the link.
"""
if self.endpoints is None:
return None
#(list duplication)
others = []; others.extend(self.endpoints)
try:
others.remove(endpoint)
except ValueError:
pass
return others
@my_gdb.virtual
[docs] def details(self):
return ""
def __str__(self):
return "%s[%s]" % (self.__class__.__name__, self.details())
@my_gdb.virtual
[docs] def get_messages(self):
return None
@staticmethod
[docs] def get_by_channel(channel):
for link in Link.list_:
if link.match_channel(channel):
return link
[docs]class Endpoint:
"""
Glue between an entity and a link.
:param comm_entity: Entity to wich is endpoint is bound.
:var link: Link to wich this endpoint if connected, if any.
:var siblings: Endpoints identical to this one.
"""
def __init__(self, comm_entity):
self.comm_entity = comm_entity
if self not in self.comm_entity.endpoints:
self.comm_entity.endpoints.append(self)
self.link = None
self.siblings = []
self.stop_next_requests = []
self.breakpoint_rq = None
self.init()
@my_gdb.virtual
[docs] def init(self):
"""
Initialization hook point for subclasses.
"""
pass
@my_gdb.internal
[docs] def comm_ent_exit(self):
"""
Notifies the endpoint that its entity exited.
Informs the associated link, if any.
"""
if self.link is not None:
self.link.endpoint_closed(self)
self.comm_entity = None
[docs] def link_created(self, link):
"""
Notifies the end endpoint that it is now connected to `link`.
Informs the associated link, if any.
:param link: the link connected to this endpoint.
:type link: Link
"""
assert isinstance(link, Link)
assert self.link is None
self.link = link
link.add_endpoint(self)
assert self in self.link.endpoints
[docs] def link_closed(self):
"""
Notifies the end endpoint that its link was closed.
Destructs the binding to the link and close the endpoint.
"""
self.link_destructed()
self.endpoint_closed()
[docs] def link_destructed(self):
"""
Destructs the binding to the link.
"""
self.link = None
[docs] def endpoint_closed(self):
"""
Closes the endpoint. Unbinds the link, and tells the entity.
"""
if self.link is not None:
link = self.link
self.link = None
link.endpoint_closed(self)
if self.comm_entity is not None:
comm_ent = self.comm_entity
self.comm_entity = None
comm_ent.endpoint_closed(self)
[docs] def stop_next(self, message=None, permanent=False, cb=None):
"""
Requests an execution stop next time a message is carried over this endpoint.
All the parameters are optional.
:param message: Message to display when the execution is stopped.
:type message: String.
:param permanent: False if the request should be destroyed after the first stop, True otherwise. Default: False.
:type permanent: Boolean
:param cb: Function callback applied to the stop reason, that returns a stop message.
:type cb: <string> Callable(<string>)
:returns: StopRequest(arg, permanent, cb)
:rtype: StopRequest.
"""
rq = StopRequest(arg, permanent, cb)
self.stop_next_requests.append(rq)
return rq
@my_gdb.internal
[docs] def get_stop_next(self, reason=None):
"""
Tells if this endpoint wants the execution to be stopped.
:returns: #1 True if the execution should stop, False otherwise.
:returns: #2 List of stop messages.
:rtype: (#1:Boolean, #2List)
"""
requests = self.stop_next_requests
self.stop_next_requests = []
stop_msgs = []
do_stop = False
for rq in requests:
do_stop = True
if rq.cb is not None:
stop_msgs.append(rq.cb(reason))
if rq.message is not None:
stop_msgs.append(rq.string)
if rq.permanent:
self.stop_next_requests.append(rq)
return do_stop, stop_msgs
@my_gdb.internal
[docs] def undo_request(self, rq):
"""
Cancels a stop request.
:param rq: a stop request generated by `self.stop_next`
:type rq: StopRequest
"""
return self.stop_next_requests.remove(rq)
@my_gdb.Listed
@my_gdb.Switchable
@my_gdb.Numbered
@my_gdb.Numbered
[docs]class Message:
"""
Object transmitted between entities, over communication endpoints and links.
:param id: identifier of the message's creation point
:param payload: the content of the message.
:param name: the name of the message.
:var breakpoint: True if the execution should be stopped when this message is processed.
:var breakpoint_cnt: Breakpoint counter, incremented each time this message stops the execution.
:var checkpoints: list of (id, payload) checkpoint crossed by this message.
"""
def __init__(self, id_, payload=None, name=None):
self.name = name
self.checkpoints = [(id_, payload)]
self.breakpoint = False
self.breakpoint_cnt = 0
[docs] def checkpoint(self, id_, payload=None):
"""
Adds a new checkpoint on the way of the message.
:param id: identifier of this checkpoint
:param payload: content of the message at this checkpoint. If the `payload` is `None`, we reuse the previous one.
"""
if payload is None:
last_id, payload = self.last_checkpoint()
self.checkpoints.append((id_, payload))
[docs] def last_checkpoint(self):
"""
:returns: the last checkpoint crossed by this message.
:rtype: (id, payload)
"""
return self.checkpoints[-1]
[docs] def last_payload(self):
"""
:returns: the payload of the last checkpoint crossed by this message.
"""
return self.checkpoints[-1][1]
def __str__(self):
return "Message[%s]" % self.name
[docs] def check_breakpoint(self, src, dst):
"""
Requests an execution stop to GDB if this message is breakpointed.
:param src: Source of the message's last hope.
:param dst: Destination of the message's last hope.
"""
if self.breakpoint:
self.breakpoint_cnt += 1
my_gdb.push_stop_request(
"[Message breakpoint for '%s': %s --> %s]" % (self, src, dst))
@staticmethod
@my_gdb.generator
[docs] def all_messages():
"""
Generator. Get all the messages stored in the entities and links.
:returns: Iterator yielding all the available messages.
"""
for ent in CommEntity.list_:
for msg in ent.get_messages():
yield msg
for link in Link.list_:
for msg in link.get_messages():
yield msg
@my_gdb.Listed
@my_gdb.Numbered
@my_gdb.Dicted
[docs]class CommComponent(CommEntity):
"""
Extention of generic communication entities.
Represents a generic component.
"""
def __init__(self):
CommEntity.__init__(self)
@classmethod
[docs] def get_selected_component(cls, silent=False):
"""
:returns: the component currently active, or None.
:rtype: CommComponent
"""
for comp in CommComponent.list_:
if comp.get_mark() == "*":
return comp