Events -> Listeners, waitAny -> resume, addEventAndWait -> addListenerAndResume, ... (refactoring), updated experiments/plugin accordingly, coding-style fixed.
git-svn-id: https://www4.informatik.uni-erlangen.de/i4svn/danceos/trunk/devel/fail@1448 8c4709b5-6ec9-48aa-a5cd-a96041d1645a
This commit is contained in:
@ -1,16 +1,15 @@
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "BufferCache.hpp"
|
||||
#include "Event.hpp"
|
||||
#include "EventManager.hpp"
|
||||
#include "Listener.hpp"
|
||||
#include "ListenerManager.hpp"
|
||||
|
||||
namespace fail {
|
||||
|
||||
template<class T>
|
||||
typename BufferCache<T>::iterator BufferCache<T>::makeActive(EventManager &ev_list, BufferCache<T>::iterator idx)
|
||||
typename BufferCache<T>::iterator BufferCache<T>::makeActive(ListenerManager &ev_list, BufferCache<T>::iterator idx)
|
||||
{
|
||||
assert(idx != end() &&
|
||||
"FATAL ERROR: Index larger than cache!");
|
||||
assert(idx != end() && "FATAL ERROR: Index larger than cache!");
|
||||
T ev = *idx;
|
||||
assert(ev && "FATAL ERROR: Object pointer cannot be NULL!");
|
||||
ev->decreaseCounter();
|
||||
@ -20,14 +19,14 @@ typename BufferCache<T>::iterator BufferCache<T>::makeActive(EventManager &ev_li
|
||||
ev->resetCounter();
|
||||
// Note: This is the one and only situation in which remove() should NOT
|
||||
// store the removed item in the delete-list.
|
||||
EventManager::iterator it = std::find(ev_list.begin(), ev_list.end(), static_cast<BaseEvent*>(ev));
|
||||
ev_list.m_remove(it, true); // remove event from buffer-list
|
||||
ListenerManager::iterator it = std::find(ev_list.begin(), ev_list.end(), static_cast<BaseListener*>(ev));
|
||||
ev_list.m_remove(it, true); // remove listener from buffer-list
|
||||
ev_list.m_FireList.push_back(ev);
|
||||
return erase(idx);
|
||||
}
|
||||
|
||||
// Declare here whatever instances of the template you are going to use:
|
||||
template class BufferCache<BPEvent*>;
|
||||
template class BufferCache<IOPortEvent*>;
|
||||
template class BufferCache<BPListener*>;
|
||||
template class BufferCache<IOPortListener*>;
|
||||
|
||||
} // end-of-namespace: fail
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
namespace fail {
|
||||
|
||||
class EventManager;
|
||||
class ListenerManager;
|
||||
|
||||
/**
|
||||
* \class BufferCache
|
||||
@ -14,19 +14,19 @@ class EventManager;
|
||||
* \brief A simple dynamic array
|
||||
*
|
||||
* This class is intended to serve as a kind of cache for the
|
||||
* untyped and therefore quite slow event handling mechanism of Fail*.
|
||||
* untyped and therefore quite slow listener handling mechanism of Fail*.
|
||||
*/
|
||||
template<class T>
|
||||
class BufferCache {
|
||||
public:
|
||||
/**
|
||||
* The list type inherent to this class. Like bufferlist_t in EventManager.hpp,
|
||||
* The list type inherent to this class. Like bufferlist_t in ListenerManager.hpp,
|
||||
* but dynamically typed.
|
||||
*/
|
||||
typedef std::list<T> cachelist_t;
|
||||
/**
|
||||
* The iterator of this class used to loop through the list of
|
||||
* added events. To retrieve an iterator to the first element, call
|
||||
* added listeners. To retrieve an iterator to the first element, call
|
||||
* begin(). end() returns the iterator, pointing after the last element.
|
||||
* (This behaviour equals the STL iterator in C++.)
|
||||
*/
|
||||
@ -79,14 +79,14 @@ public:
|
||||
*/
|
||||
inline iterator end() { return m_Buffer.end(); }
|
||||
/**
|
||||
* Acts as a replacement for EventManager::makeActive, manipulating
|
||||
* the buffer cache exclusively. EventManager::fireActiveEvents needs
|
||||
* to be called to fire the active events (see there).
|
||||
* This method is declared as a friend method in EventManager.
|
||||
* @param idx the index of the event to trigger
|
||||
* Acts as a replacement for ListenerManager::makeActive, manipulating
|
||||
* the buffer cache exclusively. ListenerManager::triggerActiveListeners needs
|
||||
* to be called to fire the active listeners (see there).
|
||||
* This method is declared as a friend method in ListenerManager.
|
||||
* @param idx the index of the listener to trigger
|
||||
* @returns an updated index which can be used to update a loop counter
|
||||
*/
|
||||
iterator makeActive(EventManager &ev_list, iterator idx);
|
||||
iterator makeActive(ListenerManager &ev_list, iterator idx);
|
||||
};
|
||||
|
||||
} // end-of-namespace: fail
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
if(BUILD_BOCHS)
|
||||
set(SRCS
|
||||
BufferCache.cc
|
||||
Event.cc
|
||||
EventManager.cc
|
||||
Listener.cc
|
||||
ListenerManager.cc
|
||||
Memory.cc
|
||||
Register.cc
|
||||
SimulatorController.cc
|
||||
bochs/BochsController.cc
|
||||
bochs/BochsEvents.cc
|
||||
bochs/BochsListener.cc
|
||||
)
|
||||
else()
|
||||
set(SRCS
|
||||
BufferCache.cc
|
||||
Event.cc
|
||||
EventManager.cc
|
||||
Listener.cc
|
||||
ListenerManager.cc
|
||||
Memory.cc
|
||||
Register.cc
|
||||
SimulatorController.cc
|
||||
|
||||
@ -1,196 +0,0 @@
|
||||
#include <set>
|
||||
|
||||
#include "EventManager.hpp"
|
||||
#include "SALInst.hpp"
|
||||
|
||||
namespace fail {
|
||||
|
||||
void EventManager::addToCaches(BaseEvent *ev)
|
||||
{
|
||||
BPEvent *bps_ev;
|
||||
if ((bps_ev = dynamic_cast<BPEvent*>(ev)) != NULL)
|
||||
m_Bp_cache.add(bps_ev);
|
||||
|
||||
IOPortEvent *io_ev;
|
||||
if ((io_ev = dynamic_cast<IOPortEvent*>(ev)) != NULL)
|
||||
m_Io_cache.add(io_ev);
|
||||
}
|
||||
|
||||
void EventManager::removeFromCaches(BaseEvent *ev)
|
||||
{
|
||||
BPEvent *bpr_ev;
|
||||
if ((bpr_ev = dynamic_cast<BPEvent*>(ev)) != NULL)
|
||||
m_Bp_cache.remove(bpr_ev);
|
||||
|
||||
IOPortEvent *io_ev;
|
||||
if ((io_ev = dynamic_cast<IOPortEvent*>(ev)) != NULL)
|
||||
m_Io_cache.remove(io_ev);
|
||||
}
|
||||
|
||||
void EventManager::clearCaches()
|
||||
{
|
||||
m_Bp_cache.clear();
|
||||
m_Io_cache.clear();
|
||||
}
|
||||
|
||||
void EventManager::add(BaseEvent* ev, ExperimentFlow* pExp)
|
||||
{
|
||||
assert(ev != NULL && "FATAL ERROR: Event (of base type BaseEvent*) cannot be NULL!");
|
||||
// a zero counter does not make sense
|
||||
assert(ev->getCounter() != 0);
|
||||
ev->setParent(pExp); // event is linked to experiment flow
|
||||
|
||||
addToCaches(ev);
|
||||
m_BufferList.push_back(ev);
|
||||
}
|
||||
|
||||
void EventManager::remove(BaseEvent* ev)
|
||||
{
|
||||
// possible cases:
|
||||
// - ev == 0 -> remove all events
|
||||
// * clear m_BufferList
|
||||
// * copy m_FireList to m_DeleteList
|
||||
if (ev == 0) {
|
||||
for (bufferlist_t::iterator it = m_BufferList.begin(); it != m_BufferList.end(); it++)
|
||||
(*it)->onEventDeletion();
|
||||
for (firelist_t::iterator it = m_FireList.begin(); it != m_FireList.end(); it++)
|
||||
(*it)->onEventDeletion();
|
||||
clearCaches();
|
||||
m_BufferList.clear();
|
||||
// all remaining active events must not fire anymore
|
||||
m_DeleteList.insert(m_DeleteList.end(), m_FireList.begin(), m_FireList.end());
|
||||
|
||||
// - ev != 0 -> remove single event
|
||||
// * find/remove ev in m_BufferList
|
||||
// * if ev in m_FireList, copy to m_DeleteList
|
||||
} else {
|
||||
ev->onEventDeletion();
|
||||
|
||||
removeFromCaches(ev);
|
||||
m_BufferList.remove(ev);
|
||||
firelist_t::const_iterator it =
|
||||
std::find(m_FireList.begin(), m_FireList.end(), ev);
|
||||
if (it != m_FireList.end()) {
|
||||
m_DeleteList.push_back(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EventManager::iterator EventManager::remove(iterator it)
|
||||
{
|
||||
return m_remove(it, false);
|
||||
}
|
||||
|
||||
EventManager::iterator EventManager::m_remove(iterator it, bool skip_deletelist)
|
||||
{
|
||||
if (!skip_deletelist) {
|
||||
// If skip_deletelist = true, m_remove was called from makeActive. Accordingly, we
|
||||
// are not going to delete an event, instead we are "moving" an event object (= *it)
|
||||
// from the buffer list to the fire-list. Therefore we only need to call the simulator's
|
||||
// event handler (onEventDeletion), if m_remove is called with the primary intention
|
||||
// to *delete* (not "move") an event.
|
||||
(*it)->onEventDeletion();
|
||||
m_DeleteList.push_back(*it);
|
||||
|
||||
// Cached events have their own BufferCache<T>::makeActive() implementation, which
|
||||
// calls this method and afterwards erase() in the cache class. This is why, when
|
||||
// called from any kind of makeActive() method, it is unnecessary to call
|
||||
// BufferCache<T>::remove() from m_remove().
|
||||
|
||||
// NOTE: in case the semantics of skip_deletelist change, please adapt the following lines
|
||||
removeFromCaches(*it);
|
||||
}
|
||||
|
||||
return m_BufferList.erase(it);
|
||||
}
|
||||
|
||||
void EventManager::remove(ExperimentFlow* flow)
|
||||
{
|
||||
// all events?
|
||||
if (flow == 0) {
|
||||
for (bufferlist_t::iterator it = m_BufferList.begin();
|
||||
it != m_BufferList.end(); it++)
|
||||
(*it)->onEventDeletion(); // invoke event handler
|
||||
clearCaches();
|
||||
m_BufferList.clear();
|
||||
} else { // remove all events corresponding to a specific experiment ("flow"):
|
||||
for (bufferlist_t::iterator it = m_BufferList.begin();
|
||||
it != m_BufferList.end(); ) {
|
||||
if ((*it)->getParent() == flow) {
|
||||
(*it)->onEventDeletion();
|
||||
it = m_BufferList.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
// events that just fired / are about to fire ...
|
||||
for (firelist_t::const_iterator it = m_FireList.begin();
|
||||
it != m_FireList.end(); it++) {
|
||||
if (std::find(m_DeleteList.begin(), m_DeleteList.end(), *it)
|
||||
!= m_DeleteList.end()) {
|
||||
continue; // (already in the delete-list? -> skip!)
|
||||
}
|
||||
// ... need to be pushed into m_DeleteList, as we're currently
|
||||
// iterating over m_FireList in fireActiveEvents() and cannot modify it
|
||||
if (flow == 0 || (*it)->getParent() == flow) {
|
||||
(*it)->onEventDeletion();
|
||||
m_DeleteList.push_back(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EventManager::~EventManager()
|
||||
{
|
||||
// nothing to do here yet
|
||||
}
|
||||
|
||||
EventManager::iterator EventManager::makeActive(iterator it)
|
||||
{
|
||||
assert(it != m_BufferList.end() &&
|
||||
"FATAL ERROR: Iterator has already reached the end!");
|
||||
BaseEvent* ev = *it;
|
||||
assert(ev && "FATAL ERROR: Event object pointer cannot be NULL!");
|
||||
ev->decreaseCounter();
|
||||
if (ev->getCounter() > 0) {
|
||||
return ++it;
|
||||
}
|
||||
ev->resetCounter();
|
||||
// Note: This is the one and only situation in which remove() should NOT
|
||||
// store the removed item in the delete-list.
|
||||
iterator it_next = m_remove(it, true); // remove event from buffer-list
|
||||
m_FireList.push_back(ev);
|
||||
return it_next;
|
||||
}
|
||||
|
||||
void EventManager::fireActiveEvents()
|
||||
{
|
||||
for (firelist_t::iterator it = m_FireList.begin();
|
||||
it != m_FireList.end(); it++) {
|
||||
if (std::find(m_DeleteList.begin(), m_DeleteList.end(), *it)
|
||||
== m_DeleteList.end()) { // not found in delete-list?
|
||||
m_pFired = *it;
|
||||
// Inform (call) the simulator's (internal) event handler that we are about
|
||||
// to trigger an event (*before* we actually toggle the experiment flow):
|
||||
m_pFired->onEventTrigger();
|
||||
ExperimentFlow* pFlow = m_pFired->getParent();
|
||||
assert(pFlow && "FATAL ERROR: The event has no parent experiment (owner)!");
|
||||
simulator.m_Flows.toggle(pFlow);
|
||||
}
|
||||
}
|
||||
m_FireList.clear();
|
||||
m_DeleteList.clear();
|
||||
// Note: Do NOT call any event handlers here!
|
||||
}
|
||||
|
||||
size_t EventManager::getContextCount() const
|
||||
{
|
||||
std::set<ExperimentFlow*> uniqueFlows; // count unique ExperimentFlow-ptr
|
||||
for (bufferlist_t::const_iterator it = m_BufferList.begin();
|
||||
it != m_BufferList.end(); it++)
|
||||
uniqueFlows.insert((*it)->getParent());
|
||||
|
||||
return uniqueFlows.size();
|
||||
}
|
||||
|
||||
} // end-of-namespace: fail
|
||||
@ -1,222 +0,0 @@
|
||||
#ifndef __EVENT_MANAGER_HPP__
|
||||
#define __EVENT_MANAGER_HPP__
|
||||
|
||||
#include <cassert>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Event.hpp"
|
||||
#include "BufferCache.hpp"
|
||||
|
||||
namespace fail {
|
||||
|
||||
class ExperimentFlow;
|
||||
|
||||
/**
|
||||
* Buffer-list for a specific experiment; acts as a simple storage container
|
||||
* for events to watch for:
|
||||
*/
|
||||
typedef std::list<BaseEvent*> bufferlist_t;
|
||||
/**
|
||||
* List of events that match the current simulator event; these events will
|
||||
* be triggered next (the list is used temporarily).
|
||||
*/
|
||||
typedef std::vector<BaseEvent*> firelist_t;
|
||||
/**
|
||||
* List of events that have been deleted during a toggled experiment flow while
|
||||
* triggering the events in the fire-list. This list is used to skip already
|
||||
* deleted events (therefore it is used temporarily either).
|
||||
*/
|
||||
typedef std::vector<BaseEvent*> deletelist_t;
|
||||
|
||||
/**
|
||||
* Cache classes for the most commonly used types of events, utilising static typing.
|
||||
* Apart from that, they work like bufferlist_t.
|
||||
*/
|
||||
typedef BufferCache<BPEvent*> bp_cache_t;
|
||||
typedef bp_cache_t::iterator bp_iter_t;
|
||||
typedef BufferCache<IOPortEvent*> io_cache_t;
|
||||
typedef io_cache_t::iterator io_iter_t;
|
||||
/**
|
||||
* \class EventManager
|
||||
*
|
||||
* \brief This class manages the events of the Fail* implementation.
|
||||
*
|
||||
* If a event is triggered, the internal data structure will be updated (id est,
|
||||
* the event will be removed from the so called buffer-list and added to the
|
||||
* fire-list). Additionally, if an experiment-flow deletes an "active" event
|
||||
* which is currently stored in the fire-list, the event (to be removed) will
|
||||
* be added to a -so called- delete-list. This ensures to prevent triggering
|
||||
* "active" events which have already been deleted by a previous experiment
|
||||
* flow. (See makeActive() and fireActiveEvent() for implementation specific
|
||||
* details.) EventManager is part of the SimulatorController and "outsources"
|
||||
* it's event management.
|
||||
*/
|
||||
class EventManager {
|
||||
private:
|
||||
// TODO: List separation of "critical types"? Hashing/sorted lists? (-> performance!)
|
||||
bufferlist_t m_BufferList; //!< the storage for events added by exp.
|
||||
firelist_t m_FireList; //!< the active events (used temporarily)
|
||||
deletelist_t m_DeleteList; //!< the deleted events (used temporarily)
|
||||
BaseEvent* m_pFired; //!< the recently fired Event-object
|
||||
bp_cache_t m_Bp_cache; //!< the storage cache for breakpoint events
|
||||
io_cache_t m_Io_cache; //!< the storage cache for port i/o events
|
||||
friend bp_iter_t bp_cache_t::makeActive(EventManager &ev_list, bp_iter_t idx);
|
||||
friend io_iter_t io_cache_t::makeActive(EventManager &ev_list, io_iter_t idx);
|
||||
public:
|
||||
/**
|
||||
* The iterator of this class used to loop through the list of
|
||||
* added events. To retrieve an iterator to the first element, call
|
||||
* begin(). end() returns the iterator, pointing after the last element.
|
||||
* (This behaviour equals the STL iterator in C++.)
|
||||
*/
|
||||
typedef bufferlist_t::iterator iterator;
|
||||
|
||||
EventManager() : m_pFired(NULL) { }
|
||||
~EventManager();
|
||||
/**
|
||||
* Adds the specified event object for the given ExperimentFlow to the
|
||||
* list of events to be watched for.
|
||||
* @param ev pointer to the event object to be added (cannot be \c NULL)
|
||||
* @param pExp the event context (a pointer to the experiment object
|
||||
* which is interested in such events, cannot be \c NULL)
|
||||
* @return the id of the added event object, that is ev->getId()
|
||||
*/
|
||||
void add(BaseEvent* ev, ExperimentFlow* pExp);
|
||||
/**
|
||||
* Removes the event based upon the specified \a ev pointer (requires
|
||||
* to loop through the whole buffer-list).
|
||||
* @param ev the pointer of the event to be removed; if ev is set to
|
||||
* \c NULL, all events (for \a all experiments) will be
|
||||
* removed
|
||||
*/
|
||||
void remove(BaseEvent* ev);
|
||||
/**
|
||||
* Behaves like remove(BaseEvent*) and additionally updates the provided
|
||||
* iterator.
|
||||
* @param it the iterator pointing to the Event object to be removed
|
||||
* @return the updated iterator which will point to the next element
|
||||
*/
|
||||
iterator remove(iterator it);
|
||||
private:
|
||||
/**
|
||||
* Internal implementation of remove(iterator it) that allows
|
||||
* to skip the delete-list.
|
||||
* @param it the iterator pointing to the Event object to be removed
|
||||
* @param skip_deletelist \c true to skip the deletion of the Event object
|
||||
* or \false to behave like \c remove(iterator)
|
||||
* @return the updated iterator which will point to the next element
|
||||
*/
|
||||
iterator m_remove(iterator it, bool skip_deletelist);
|
||||
public:
|
||||
/**
|
||||
* Returns an iterator to the beginning of the internal data structure.
|
||||
* Don't forget to update the returned iterator when calling one of the
|
||||
* modifying methods like makeActive() or remove(). Therefore you need
|
||||
* to call the iterator-based variants of makeActive() and remove().
|
||||
* \code
|
||||
* [X|1|2| ... |n]
|
||||
* ^
|
||||
* \endcode
|
||||
* @return iterator to the beginning
|
||||
*/
|
||||
iterator begin() { return (m_BufferList.begin()); }
|
||||
/**
|
||||
* Returns an iterator to the end of the interal data structure.
|
||||
* Don't forget to update the returned iterator when calling one of the
|
||||
* modifying methods like makeActive() or remove(). Therefore you need
|
||||
* to call the iterator-based variants of makeActive() and remove().
|
||||
* \code
|
||||
* [1|2| ... |n]X
|
||||
* ^
|
||||
* \endcode
|
||||
* @return iterator to the end
|
||||
*/
|
||||
iterator end() { return (m_BufferList.end()); }
|
||||
/**
|
||||
* Removes all events for the specified experiment.
|
||||
* @param flow pointer to experiment context (0 = all experiments)
|
||||
*/
|
||||
void remove(ExperimentFlow* flow);
|
||||
/**
|
||||
* Retrieves the number of experiments which currently have active
|
||||
* events. This number is trivially equal to the (current) total
|
||||
* number of ExperimentFlow-objects.
|
||||
* @return number of experiments having active events
|
||||
*/
|
||||
size_t getContextCount() const;
|
||||
/**
|
||||
* Retrieves the total number of buffered events. This doesn't include
|
||||
* the events in the fire- or delete-list.
|
||||
* @return the total event count (for all flows)
|
||||
*/
|
||||
size_t getEventCount() const { return m_BufferList.size(); }
|
||||
/**
|
||||
* Retrieves the recently triggered event object. To map this object to
|
||||
* it's context (id est, the related ExerimentFlow), use
|
||||
* \c getLastFiredDest().
|
||||
* @return a pointer to the recent event or \c NULL if nothing has been
|
||||
* triggered so far
|
||||
*/
|
||||
BaseEvent* getLastFired() { return (m_pFired); }
|
||||
/**
|
||||
* Retrieves the ExperimentFlow-object for the given BaseEvent (it's
|
||||
* \a context).
|
||||
* @param pEv the event object to be looked up
|
||||
* @return a pointer to the context of \a pEv or \c NULL if the
|
||||
* corresponding context could not be found
|
||||
*/
|
||||
ExperimentFlow* getExperimentOf(BaseEvent* pEv);
|
||||
/**
|
||||
* Moves the events from the (internal) buffer-list to the fire-list.
|
||||
* To actually fire the events, call fireActiveEvents().
|
||||
* Returns an updated iterator which points to the next element.
|
||||
* @param ev the event to trigger
|
||||
* @return returns the updated iteration, pointing to the next element
|
||||
* after makeActive returns, "it" is invalid, so the returned
|
||||
* iterator should be used to continue the iteration
|
||||
*
|
||||
* TODO: Improve naming (instead of "makeActive")?
|
||||
*/
|
||||
iterator makeActive(iterator it);
|
||||
/**
|
||||
* Triggers the active events. Each event is triggered if it has not
|
||||
* recently been removed (id est: is not found in the delete-list). See
|
||||
* makeActive() for more details. The recently triggered event can be
|
||||
* retrieved by calling \a getLastFired(). After all events have been
|
||||
* triggered, the (internal) fire- and delete-list will be cleared.
|
||||
*
|
||||
* TODO: Improve naming (instead of "fireActiveEvents")?
|
||||
*/
|
||||
void fireActiveEvents();
|
||||
/**
|
||||
* Retrieves the BPEvent buffer cache.
|
||||
* @returns the buffer cache
|
||||
*/
|
||||
inline bp_cache_t &getBPBuffer() { return m_Bp_cache; }
|
||||
/**
|
||||
* Retrieves the IOPortEvent buffer cache.
|
||||
* @returns the buffer cache
|
||||
*/
|
||||
inline io_cache_t &getIOBuffer() { return m_Io_cache; }
|
||||
private:
|
||||
/**
|
||||
* Add an event to its appropriate cache.
|
||||
* @param the event to add
|
||||
*/
|
||||
void addToCaches(BaseEvent *ev);
|
||||
/**
|
||||
* Remove an event from its cache.
|
||||
* @param the event to remove
|
||||
*/
|
||||
void removeFromCaches(BaseEvent *ev);
|
||||
/**
|
||||
* Clear the event caches.
|
||||
*/
|
||||
void clearCaches();
|
||||
};
|
||||
|
||||
} // end-of-namespace: fail
|
||||
|
||||
#endif // __EVENT_MANAGER_HPP__
|
||||
@ -1,9 +1,9 @@
|
||||
#include "Event.hpp"
|
||||
#include "Listener.hpp"
|
||||
#include "SALInst.hpp"
|
||||
|
||||
namespace fail {
|
||||
|
||||
bool TroubleEvent::isMatching(unsigned troubleNum) const
|
||||
bool TroubleListener::isMatching(unsigned troubleNum) const
|
||||
{
|
||||
for (unsigned i = 0; i < m_WatchNumbers.size(); i++) {
|
||||
if (m_WatchNumbers[i] == troubleNum ||
|
||||
@ -13,7 +13,7 @@ bool TroubleEvent::isMatching(unsigned troubleNum) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TroubleEvent::removeWatchNumber(unsigned troubleNum)
|
||||
bool TroubleListener::removeWatchNumber(unsigned troubleNum)
|
||||
{
|
||||
for (unsigned i = 0; i < m_WatchNumbers.size(); i++) {
|
||||
if (m_WatchNumbers[i] == troubleNum) {
|
||||
@ -24,7 +24,7 @@ bool TroubleEvent::removeWatchNumber(unsigned troubleNum)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TroubleEvent::addWatchNumber(unsigned troubleNumber)
|
||||
bool TroubleListener::addWatchNumber(unsigned troubleNumber)
|
||||
{
|
||||
for (unsigned i = 0; i < m_WatchNumbers.size(); i++) {
|
||||
if (m_WatchNumbers[i] == troubleNumber)
|
||||
@ -34,7 +34,7 @@ bool TroubleEvent::addWatchNumber(unsigned troubleNumber)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemAccessEvent::isMatching(address_t addr, accessType_t accesstype) const
|
||||
bool MemAccessListener::isMatching(address_t addr, accessType_t accesstype) const
|
||||
{
|
||||
if (!(m_WatchType & accesstype))
|
||||
return false;
|
||||
@ -44,20 +44,20 @@ bool MemAccessEvent::isMatching(address_t addr, accessType_t accesstype) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BPEvent::aspaceIsMatching(address_t aspace) const
|
||||
bool BPListener::aspaceIsMatching(address_t aspace) const
|
||||
{
|
||||
if (m_CR3 == ANY_ADDR || m_CR3 == aspace)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void BPRangeEvent::setWatchInstructionPointerRange(address_t start, address_t end)
|
||||
void BPRangeListener::setWatchInstructionPointerRange(address_t start, address_t end)
|
||||
{
|
||||
m_WatchStartAddr = start;
|
||||
m_WatchEndAddr = end;
|
||||
}
|
||||
|
||||
bool BPRangeEvent::isMatching(address_t addr, address_t aspace) const
|
||||
bool BPRangeListener::isMatching(address_t addr, address_t aspace) const
|
||||
{
|
||||
if (!aspaceIsMatching(aspace))
|
||||
return false;
|
||||
@ -67,7 +67,7 @@ bool BPRangeEvent::isMatching(address_t addr, address_t aspace) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BPSingleEvent::isMatching(address_t addr, address_t aspace) const
|
||||
bool BPSingleListener::isMatching(address_t addr, address_t aspace) const
|
||||
{
|
||||
if (aspaceIsMatching(aspace)) {
|
||||
if (m_WatchInstrPtr == ANY_ADDR || m_WatchInstrPtr == addr)
|
||||
@ -1,5 +1,5 @@
|
||||
#ifndef __EVENT_HPP__
|
||||
#define __EVENT_HPP__
|
||||
#ifndef __LISTENER_HPP__
|
||||
#define __LISTENER_HPP__
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
@ -14,7 +14,7 @@ namespace fail {
|
||||
|
||||
class ExperimentFlow;
|
||||
|
||||
//! address wildcard (e.g. for BPEvent's)
|
||||
//! address wildcard (e.g. for BPListener's)
|
||||
const address_t ANY_ADDR = static_cast<address_t>(-1);
|
||||
//! instruction wildcard
|
||||
const unsigned ANY_INSTR = static_cast<unsigned>(-1);
|
||||
@ -24,58 +24,59 @@ const unsigned ANY_TRAP = static_cast<unsigned>(-1);
|
||||
const unsigned ANY_INTERRUPT = static_cast<unsigned>(-1);
|
||||
|
||||
/**
|
||||
* \class BaseEvent
|
||||
* This is the base class for all event types.
|
||||
* \class BaseListener
|
||||
* This is the base class for all listener types.
|
||||
*/
|
||||
class BaseEvent {
|
||||
class BaseListener {
|
||||
protected:
|
||||
time_t m_tStamp; //!< time stamp of event
|
||||
unsigned int m_OccCounter; //!< event fires when 0 is reached
|
||||
time_t m_tStamp; //!< time stamp of listener
|
||||
unsigned int m_OccCounter; //!< listener fires when 0 is reached
|
||||
unsigned int m_OccCounterInit; //!< initial value for m_OccCounter
|
||||
ExperimentFlow* m_Parent; //!< this event belongs to experiment m_Parent
|
||||
ExperimentFlow* m_Parent; //!< this listener belongs to experiment m_Parent
|
||||
public:
|
||||
BaseEvent() : m_OccCounter(1), m_OccCounterInit(1), m_Parent(NULL)
|
||||
BaseListener() : m_OccCounter(1), m_OccCounterInit(1), m_Parent(NULL)
|
||||
{ updateTime(); }
|
||||
virtual ~BaseEvent() { }
|
||||
virtual ~BaseListener() { }
|
||||
/**
|
||||
* This method is called when an experiment flow adds a new event by
|
||||
* calling \c simulator.addEvent(pev) or \c simulator.addEventAndWait(pev).
|
||||
* More specifically, the event will be added to the event-list first
|
||||
* (buffer-list, to be precise) and then this event handler is called.
|
||||
* This method is called when an experiment flow adds a new listener by
|
||||
* calling \c simulator.addListener() or \c simulator.addListenerAndResume().
|
||||
* More specifically, the listener will be added to the listener manager first
|
||||
* (buffer-list, to be precise) and then this listener handler is called.
|
||||
* @return You should return \c true to continue and \c false to prevent
|
||||
* the addition of the event \a pev, yielding an error in the
|
||||
* experiment flow (i.e. \c INVALID_EVENT is returned).
|
||||
* the addition of the listener \c this, yielding an error in the
|
||||
* experiment flow (i.e., \c false is returned).
|
||||
*/
|
||||
virtual bool onEventAddition() { return true; }
|
||||
virtual bool onAddition() { return true; }
|
||||
/**
|
||||
* This method is called when an experiment flow removes an event from
|
||||
* the event-management by calling \c removeEvent(prev), \c clearEvents()
|
||||
* or by deleting a complete flow (\c removeFlow). More specifically, this
|
||||
* event handler will be called *before* the event is actually deleted.
|
||||
* This method is called when an experiment flow removes an listener from
|
||||
* the listener-management by calling \c removeListener(), \c clearListeners()
|
||||
* or by deleting a complete flow (\c removeFlow()). More specifically, this
|
||||
* listener handler will be called *before* the listener is actually deleted.
|
||||
*/
|
||||
virtual void onEventDeletion() { }
|
||||
virtual void onDeletion() { }
|
||||
/**
|
||||
* This method is called when an previously added event is about to be
|
||||
* triggered by the simulator-backend. More specifically, this event handler
|
||||
* will be called *before* the event is actually triggered, i.e. before the
|
||||
* This method is called when an previously added listener is about to be
|
||||
* triggered by the simulator-backend. More specifically, this listener handler
|
||||
* will be called *before* the listener is actually triggered, i.e. before the
|
||||
* corresponding coroutine is toggled.
|
||||
*/
|
||||
virtual void onEventTrigger() { }
|
||||
virtual void onTrigger() { }
|
||||
// TODO: Hier noch ne neue Methode oder reicht es, die Semantik von onTrigger umzudef.?
|
||||
/**
|
||||
* Retrieves the time stamp of this event. The time stamp is set when
|
||||
* the event gets created, id est the constructor is called. The meaning
|
||||
* of this value depends on the actual event type.
|
||||
* Retrieves the time stamp of this listener. The time stamp is set when
|
||||
* the listener gets created, i.e. the constructor is called. The meaning
|
||||
* of this value depends on the actual listener type.
|
||||
* @return the time stamp
|
||||
*/
|
||||
std::time_t getTime() const { return (m_tStamp); }
|
||||
/**
|
||||
* Decreases the event counter by one. When this counter reaches zero, the
|
||||
* event will be triggered.
|
||||
* Decreases the listener counter by one. When this counter reaches zero, the
|
||||
* listener will be triggered.
|
||||
*/
|
||||
void decreaseCounter() { --m_OccCounter; }
|
||||
/**
|
||||
* Sets the event counter to the specified value (default is one).
|
||||
* @param val the new counter value
|
||||
* Sets the listener counter to the specified value.
|
||||
* @param val the new counter value (default is 1)
|
||||
*/
|
||||
void setCounter(unsigned int val = 1) { m_OccCounter = m_OccCounterInit = val; }
|
||||
/**
|
||||
@ -84,56 +85,56 @@ public:
|
||||
*/
|
||||
unsigned int getCounter() const { return (m_OccCounter); }
|
||||
/**
|
||||
* Resets the event counter to its default value, or the last
|
||||
* value that was set through setCounter().
|
||||
* Resets the listener counter to its default value, or the last
|
||||
* value that was set through \c setCounter().
|
||||
*/
|
||||
void resetCounter() { m_OccCounter = m_OccCounterInit; }
|
||||
/**
|
||||
* Sets the time stamp for this event to the current system time.
|
||||
* Sets the time stamp for this listener to the current system time.
|
||||
*/
|
||||
void updateTime() { time(&m_tStamp); }
|
||||
/**
|
||||
* Returns the parent experiment of this event (context).
|
||||
* If the event was created temporarily or wasn't linked to a context,
|
||||
* Returns the parent experiment of this listener (context).
|
||||
* If the listener was created temporarily or wasn't linked to a context,
|
||||
* the return value is \c NULL (unknown identity).
|
||||
*/
|
||||
ExperimentFlow* getParent() const { return (m_Parent); }
|
||||
/**
|
||||
* Sets the parent (context) of this event. The default context
|
||||
* Sets the parent (context) of this listener. The default context
|
||||
* is \c NULL (unknown identity).
|
||||
*/
|
||||
void setParent(ExperimentFlow* pFlow) { m_Parent = pFlow; }
|
||||
};
|
||||
// ----------------------------------------------------------------------------
|
||||
// Specialized events:
|
||||
// Specialized listeners:
|
||||
//
|
||||
|
||||
/**
|
||||
* \class BEvent
|
||||
* A Breakpoint event to observe instruction changes within a given address space.
|
||||
* \class BListener
|
||||
* A Breakpoint listener to observe instruction changes within a given address space.
|
||||
*/
|
||||
class BPEvent : virtual public BaseEvent {
|
||||
class BPListener : virtual public BaseListener {
|
||||
private:
|
||||
address_t m_CR3;
|
||||
address_t m_TriggerInstrPtr;
|
||||
public:
|
||||
/**
|
||||
* Creates a new breakpoint event. The range information is specific to
|
||||
* Creates a new breakpoint listener. The range information is specific to
|
||||
* the subclasses.
|
||||
* @param address_space the address space to be oberserved, given as the
|
||||
* content of a CR3 register. The event will not be triggered unless
|
||||
* content of a CR3 register. The listener will not be triggered unless
|
||||
* \a ip is part of the given address space.
|
||||
* ANY_ADDR can be used as a placeholder to allow debugging
|
||||
* in a random address space.
|
||||
*/
|
||||
BPEvent(address_t address_space = ANY_ADDR)
|
||||
BPListener(address_t address_space = ANY_ADDR)
|
||||
: m_CR3(address_space), m_TriggerInstrPtr(ANY_ADDR) { }
|
||||
/**
|
||||
* Returns the address space register of this event.
|
||||
* Returns the address space register of this listener.
|
||||
*/
|
||||
address_t getCR3() const { return m_CR3; }
|
||||
/**
|
||||
* Sets the address space register for this event.
|
||||
* Sets the address space register for this listener.
|
||||
*/
|
||||
void setCR3(address_t iptr) { m_CR3 = iptr; }
|
||||
/**
|
||||
@ -145,96 +146,108 @@ public:
|
||||
*/
|
||||
virtual bool isMatching(address_t addr = 0, address_t address_space = ANY_ADDR) const = 0;
|
||||
/**
|
||||
* Returns the instruction pointer that triggered this event.
|
||||
* Returns the instruction pointer that triggered this listener.
|
||||
*/
|
||||
address_t getTriggerInstructionPointer() const { return m_TriggerInstrPtr; }
|
||||
/**
|
||||
* Sets the instruction pointer that triggered this event. Should not
|
||||
* Sets the instruction pointer that triggered this listener. Should not
|
||||
* be used by experiment code.
|
||||
*/
|
||||
void setTriggerInstructionPointer(address_t iptr) { m_TriggerInstrPtr = iptr; }
|
||||
};
|
||||
|
||||
/**
|
||||
* \class BPSingleEvent
|
||||
* A Breakpoint event to observe specific instruction pointers.
|
||||
* \class BPSingleListener
|
||||
* A Breakpoint listener to observe specific instruction pointers.
|
||||
*/
|
||||
class BPSingleEvent : virtual public BPEvent {
|
||||
class BPSingleListener : virtual public BPListener {
|
||||
private:
|
||||
address_t m_WatchInstrPtr;
|
||||
public:
|
||||
/**
|
||||
* Creates a new breakpoint event.
|
||||
/**
|
||||
* Creates a new breakpoint listener.
|
||||
* @param ip the instruction pointer of the breakpoint. If the control
|
||||
* flow reaches this address and its counter value is zero, the
|
||||
* event will be triggered. \a eip can be set to the ANY_ADDR
|
||||
* wildcard to allow arbitrary addresses.
|
||||
* listener will be triggered. \a eip can be set to the ANY_ADDR
|
||||
* wildcard to allow arbitrary addresses. Defaults to 0.
|
||||
* @param address_space the address space to be oberserved, given as the
|
||||
* content of a CR3 register. The event will not be triggered unless
|
||||
* \a ip is part of the given address space.
|
||||
* content of a CR3 register. The listener will not be triggered unless
|
||||
* \a ip is part of the given address space. Defaults to \c ANY_ADDR.
|
||||
* Here, too, ANY_ADDR is a placeholder to allow debugging
|
||||
* in a random address space.
|
||||
*/
|
||||
BPSingleEvent(address_t ip = 0, address_t address_space = ANY_ADDR)
|
||||
: BPEvent(address_space), m_WatchInstrPtr(ip) { }
|
||||
BPSingleListener(address_t ip = 0, address_t address_space = ANY_ADDR)
|
||||
: BPListener(address_space), m_WatchInstrPtr(ip) { }
|
||||
/**
|
||||
* Returns the instruction pointer this event waits for.
|
||||
* Returns the instruction pointer this listener waits for.
|
||||
* @return the instruction pointer specified in the constructor or by
|
||||
* calling \c setWatchInstructionPointer()
|
||||
*/
|
||||
address_t getWatchInstructionPointer() const
|
||||
{ return m_WatchInstrPtr; }
|
||||
/**
|
||||
* Sets the instruction pointer this event waits for.
|
||||
* Sets the instruction pointer this listener waits for.
|
||||
* @param iptr the new instruction ptr to wait for
|
||||
*/
|
||||
void setWatchInstructionPointer(address_t iptr)
|
||||
{ m_WatchInstrPtr = iptr; }
|
||||
/**
|
||||
* Checks whether a given address is matching.
|
||||
* @param addr address to check
|
||||
* @param address_space address space information
|
||||
* @return \c true if address is within the range, \c false otherwise
|
||||
*/
|
||||
bool isMatching(address_t addr, address_t address_space) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* \class BPRangeEvent
|
||||
* A event type to observe ranges of instruction pointers.
|
||||
* \class BPRangeListener
|
||||
* A listener type to observe ranges of instruction pointers.
|
||||
*/
|
||||
class BPRangeEvent : virtual public BPEvent {
|
||||
class BPRangeListener : virtual public BPListener {
|
||||
private:
|
||||
address_t m_WatchStartAddr;
|
||||
address_t m_WatchEndAddr;
|
||||
public:
|
||||
/**
|
||||
* Creates a new breakpoint-range event. The range's ends are both
|
||||
* Creates a new breakpoint-range listener. The range's ends are both
|
||||
* inclusive, i.e. an address matches if start <= addr <= end.
|
||||
* ANY_ADDR denotes the lower respectively the upper end of the address
|
||||
* space.
|
||||
*/
|
||||
BPRangeEvent(address_t start = 0, address_t end = 0, address_t address_space = ANY_ADDR)
|
||||
: BPEvent(address_space), m_WatchStartAddr(start), m_WatchEndAddr(end)
|
||||
BPRangeListener(address_t start = 0, address_t end = 0, address_t address_space = ANY_ADDR)
|
||||
: BPListener(address_space), m_WatchStartAddr(start), m_WatchEndAddr(end)
|
||||
{ }
|
||||
/**
|
||||
* Returns the instruction pointer watch range of this event.
|
||||
* Returns the instruction pointer watch range of this listener.
|
||||
* @return the listerner's range
|
||||
*/
|
||||
std::pair<address_t, address_t> getWatchInstructionPointerRange() const
|
||||
{ return std::make_pair(m_WatchStartAddr, m_WatchEndAddr); }
|
||||
/**
|
||||
* Sets the instruction pointer watch range. Both ends of the range
|
||||
* may be ANY_ADDR (cf. constructor).
|
||||
* @param start the (inclusive) lower bound, or \c ANY_ADDR
|
||||
* @param end the (inclusive) upper bound, or \c ANY_ADDR
|
||||
*/
|
||||
void setWatchInstructionPointerRange(address_t start,
|
||||
address_t end);
|
||||
void setWatchInstructionPointerRange(address_t start, address_t end);
|
||||
/**
|
||||
* Checks whether a given address is within the range.
|
||||
* @param addr address to check
|
||||
* @param address_space address space information
|
||||
* @return \c true if address is within the range (represented by
|
||||
* \c this), \c false otherwise
|
||||
*/
|
||||
bool isMatching(address_t addr, address_t address_space) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* \class MemAccessEvent
|
||||
* \class MemAccessListener
|
||||
* Observes memory read/write accesses.
|
||||
* FIXME? currently >8-bit accesses only match if their lowest address is being watched
|
||||
* (e.g., a 32-bit write to 0x4 also accesses 0x7, but this cannot be matched)
|
||||
*/
|
||||
class MemAccessEvent : virtual public BaseEvent {
|
||||
class MemAccessListener : virtual public BaseListener {
|
||||
public:
|
||||
enum accessType_t {
|
||||
MEM_UNKNOWN = 0x0,
|
||||
@ -250,7 +263,7 @@ private:
|
||||
* (MEM_READ || MEM_WRITE || MEM_READWRITE).
|
||||
*/
|
||||
accessType_t m_WatchType;
|
||||
//! Specific guest system address that actually triggered the event.
|
||||
//! Specific guest system address that actually triggered the listener.
|
||||
address_t m_TriggerAddr;
|
||||
//! Width of the memory access (# bytes).
|
||||
size_t m_TriggerWidth;
|
||||
@ -259,11 +272,11 @@ private:
|
||||
//! Memory access type at m_TriggerAddr.
|
||||
accessType_t m_AccessType;
|
||||
public:
|
||||
MemAccessEvent(accessType_t watchtype = MEM_READWRITE)
|
||||
MemAccessListener(accessType_t watchtype = MEM_READWRITE)
|
||||
: m_WatchAddr(ANY_ADDR), m_WatchType(watchtype),
|
||||
m_TriggerAddr(ANY_ADDR), m_TriggerIP(ANY_ADDR),
|
||||
m_AccessType(MEM_UNKNOWN) { }
|
||||
MemAccessEvent(address_t addr,
|
||||
MemAccessListener(address_t addr,
|
||||
accessType_t watchtype = MEM_READWRITE)
|
||||
: m_WatchAddr(addr), m_WatchType(watchtype),
|
||||
m_TriggerAddr(ANY_ADDR), m_TriggerIP(ANY_ADDR),
|
||||
@ -282,21 +295,21 @@ public:
|
||||
bool isMatching(address_t addr, accessType_t accesstype) const;
|
||||
/**
|
||||
* Returns the specific memory address that actually triggered the
|
||||
* event.
|
||||
* listener.
|
||||
*/
|
||||
address_t getTriggerAddress() const { return (m_TriggerAddr); }
|
||||
address_t getTriggerAddress() const { return m_TriggerAddr; }
|
||||
/**
|
||||
* Sets the specific memory address that actually triggered the event.
|
||||
* Sets the specific memory address that actually triggered the listener.
|
||||
* Should not be used by experiment code.
|
||||
*/
|
||||
void setTriggerAddress(address_t addr) { m_TriggerAddr = addr; }
|
||||
/**
|
||||
* Returns the specific memory address that actually triggered the
|
||||
* event.
|
||||
* listener.
|
||||
*/
|
||||
size_t getTriggerWidth() const { return (m_TriggerWidth); }
|
||||
size_t getTriggerWidth() const { return m_TriggerWidth; }
|
||||
/**
|
||||
* Sets the specific memory address that actually triggered the event.
|
||||
* Sets the specific memory address that actually triggered the listener.
|
||||
* Should not be used by experiment code.
|
||||
*/
|
||||
void setTriggerWidth(size_t width) { m_TriggerWidth = width; }
|
||||
@ -304,64 +317,62 @@ public:
|
||||
* Returns the address of the instruction causing this memory
|
||||
* access.
|
||||
*/
|
||||
address_t getTriggerInstructionPointer() const
|
||||
{ return (m_TriggerIP); }
|
||||
address_t getTriggerInstructionPointer() const { return m_TriggerIP; }
|
||||
/**
|
||||
* Sets the address of the instruction causing this memory
|
||||
* access. Should not be used by experiment code.
|
||||
*/
|
||||
void setTriggerInstructionPointer(address_t addr)
|
||||
{ m_TriggerIP = addr; }
|
||||
void setTriggerInstructionPointer(address_t addr) { m_TriggerIP = addr; }
|
||||
/**
|
||||
* Returns type (MEM_READ || MEM_WRITE) of the memory access that
|
||||
* triggered this event.
|
||||
* triggered this listener.
|
||||
*/
|
||||
accessType_t getTriggerAccessType() const { return (m_AccessType); }
|
||||
accessType_t getTriggerAccessType() const { return m_AccessType; }
|
||||
/**
|
||||
* Sets type of the memory access that triggered this event. Should
|
||||
* Sets type of the memory access that triggered this listener. Should
|
||||
* not be used by experiment code.
|
||||
*/
|
||||
void setTriggerAccessType(accessType_t type) { m_AccessType = type; }
|
||||
/**
|
||||
* Returns memory access types (MEM_READ || MEM_WRITE || MEM_READWRITE)
|
||||
* this event watches. Should not be used by experiment code.
|
||||
* this listener watches. Should not be used by experiment code.
|
||||
*/
|
||||
accessType_t getWatchAccessType() const { return (m_WatchType); }
|
||||
accessType_t getWatchAccessType() const { return m_WatchType; }
|
||||
};
|
||||
|
||||
/**
|
||||
* \class MemReadEvent
|
||||
* \class MemReadListener
|
||||
* Observes memory read accesses.
|
||||
*/
|
||||
class MemReadEvent : virtual public MemAccessEvent {
|
||||
class MemReadListener : virtual public MemAccessListener {
|
||||
public:
|
||||
MemReadEvent()
|
||||
: MemAccessEvent(MEM_READ) { }
|
||||
MemReadEvent(address_t addr)
|
||||
: MemAccessEvent(addr, MEM_READ) { }
|
||||
MemReadListener()
|
||||
: MemAccessListener(MEM_READ) { }
|
||||
MemReadListener(address_t addr)
|
||||
: MemAccessListener(addr, MEM_READ) { }
|
||||
};
|
||||
|
||||
/**
|
||||
* \class MemWriteEvent
|
||||
* \class MemWriteListener
|
||||
* Observes memory write accesses.
|
||||
*/
|
||||
class MemWriteEvent : virtual public MemAccessEvent {
|
||||
class MemWriteListener : virtual public MemAccessListener {
|
||||
public:
|
||||
MemWriteEvent()
|
||||
: MemAccessEvent(MEM_READ) { }
|
||||
MemWriteEvent(address_t addr)
|
||||
: MemAccessEvent(addr, MEM_WRITE) { }
|
||||
MemWriteListener()
|
||||
: MemAccessListener(MEM_READ) { }
|
||||
MemWriteListener(address_t addr)
|
||||
: MemAccessListener(addr, MEM_WRITE) { }
|
||||
};
|
||||
|
||||
/**
|
||||
* \class TroubleEvent
|
||||
* \class TroubleListener
|
||||
* Observes interrupt/trap activties.
|
||||
*/
|
||||
class TroubleEvent : virtual public BaseEvent {
|
||||
class TroubleListener : virtual public BaseListener {
|
||||
private:
|
||||
/**
|
||||
* Specific guest system interrupt/trap number that actually
|
||||
* trigger the event.
|
||||
* trigger the listener.
|
||||
*/
|
||||
int m_TriggerNumber;
|
||||
/**
|
||||
@ -370,8 +381,8 @@ private:
|
||||
*/
|
||||
std::vector<unsigned> m_WatchNumbers;
|
||||
public:
|
||||
TroubleEvent() : m_TriggerNumber (-1) { }
|
||||
TroubleEvent(unsigned troubleNumber)
|
||||
TroubleListener() : m_TriggerNumber (-1) { }
|
||||
TroubleListener(unsigned troubleNumber)
|
||||
: m_TriggerNumber(-1)
|
||||
{ addWatchNumber(troubleNumber); }
|
||||
/**
|
||||
@ -393,39 +404,39 @@ public:
|
||||
* Returns the list of observed numbers
|
||||
* @return a copy of the list which contains all observed numbers
|
||||
*/
|
||||
std::vector<unsigned> getWatchNumbers() { return (m_WatchNumbers); }
|
||||
std::vector<unsigned> getWatchNumbers() { return m_WatchNumbers; }
|
||||
/**
|
||||
* Checks whether a given interrupt-/trap-number is matching.
|
||||
*/
|
||||
bool isMatching(unsigned troubleNum) const;
|
||||
/**
|
||||
* Sets the specific interrupt-/trap-number that actually triggered
|
||||
* the event. Should not be used by experiment code.
|
||||
* the listener. Should not be used by experiment code.
|
||||
*/
|
||||
void setTriggerNumber(unsigned troubleNum)
|
||||
{ m_TriggerNumber = troubleNum; }
|
||||
/**
|
||||
* Returns the specific interrupt-/trap-number that actually triggered
|
||||
* the event.
|
||||
* the listener.
|
||||
*/
|
||||
unsigned getTriggerNumber() { return (m_TriggerNumber); }
|
||||
unsigned getTriggerNumber() { return m_TriggerNumber; }
|
||||
};
|
||||
|
||||
/**
|
||||
* \class InterruptEvent
|
||||
* \class InterruptListener
|
||||
* Observes interrupts of the guest system.
|
||||
*/
|
||||
class InterruptEvent : virtual public TroubleEvent {
|
||||
class InterruptListener : virtual public TroubleListener {
|
||||
private:
|
||||
bool m_IsNMI; //!< non maskable interrupt flag
|
||||
public:
|
||||
InterruptEvent() : m_IsNMI(false) { }
|
||||
InterruptEvent(unsigned interrupt) : m_IsNMI(false)
|
||||
InterruptListener() : m_IsNMI(false) { }
|
||||
InterruptListener(unsigned interrupt) : m_IsNMI(false)
|
||||
{ addWatchNumber(interrupt); }
|
||||
/**
|
||||
* Returns \c true if the interrupt is non maskable, \c false otherwise.
|
||||
*/
|
||||
bool isNMI() { return (m_IsNMI); }
|
||||
bool isNMI() { return m_IsNMI; }
|
||||
/**
|
||||
* Sets the interrupt type (non maskable or not).
|
||||
*/
|
||||
@ -433,29 +444,29 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* \class TrapEvent
|
||||
* \class TrapListener
|
||||
* Observes traps of the guest system.
|
||||
*/
|
||||
class TrapEvent : virtual public TroubleEvent {
|
||||
class TrapListener : virtual public TroubleListener {
|
||||
public:
|
||||
TrapEvent() { }
|
||||
TrapEvent(unsigned trap) { addWatchNumber(trap); }
|
||||
TrapListener() { }
|
||||
TrapListener(unsigned trap) { addWatchNumber(trap); }
|
||||
};
|
||||
|
||||
/**
|
||||
* \class GuestEvent
|
||||
* \class GuestListener
|
||||
* Used to receive data from the guest system.
|
||||
*/
|
||||
class GuestEvent : virtual public BaseEvent {
|
||||
class GuestListener : virtual public BaseListener {
|
||||
private:
|
||||
char m_Data;
|
||||
unsigned m_Port;
|
||||
public:
|
||||
GuestEvent() : m_Data(0), m_Port(0) { }
|
||||
GuestListener() : m_Data(0), m_Port(0) { }
|
||||
/**
|
||||
* Returns the data, transmitted by the guest system.
|
||||
*/
|
||||
char getData() const { return (m_Data); }
|
||||
char getData() const { return m_Data; }
|
||||
/**
|
||||
* Sets the data which had been transmitted by the guest system.
|
||||
*/
|
||||
@ -463,7 +474,7 @@ public:
|
||||
/**
|
||||
* Returns the data length, transmitted by the guest system.
|
||||
*/
|
||||
unsigned getPort() const { return (m_Port); }
|
||||
unsigned getPort() const { return m_Port; }
|
||||
/**
|
||||
* Sets the data length which had been transmitted by the guest system.
|
||||
*/
|
||||
@ -471,52 +482,52 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* \class IOPortEvent
|
||||
* \class IOPortListener
|
||||
* Observes I/O access on architectures with a separate I/O access mechanism (e.g. IA-32)
|
||||
*/
|
||||
class IOPortEvent : virtual public BaseEvent {
|
||||
class IOPortListener : virtual public BaseListener {
|
||||
private:
|
||||
unsigned char m_Data;
|
||||
unsigned m_Port;
|
||||
bool m_Out;
|
||||
public:
|
||||
/**
|
||||
* Initialises an IOPortEvent
|
||||
* Initialises an IOPortListener
|
||||
*
|
||||
* @param port the port the event ist listening on
|
||||
* @param out Defines the direction of the event.
|
||||
* @param port the port the listener ist listening on
|
||||
* @param out Defines the direction of the listener.
|
||||
* \arg \c true Output on the given port is captured.
|
||||
* \arg \c false Input on the given port is captured.
|
||||
*/
|
||||
IOPortEvent(unsigned port, bool out) : m_Data(0), m_Port(port), m_Out(out) { }
|
||||
IOPortListener(unsigned port, bool out) : m_Data(0), m_Port(port), m_Out(out) { }
|
||||
/**
|
||||
* Returns the data sent to the specified port
|
||||
*/
|
||||
unsigned char getData() const { return (m_Data); }
|
||||
unsigned char getData() const { return m_Data; }
|
||||
/**
|
||||
* Sets the data which had been transmitted.
|
||||
*/
|
||||
void setData(unsigned char data) { m_Data = data; }
|
||||
/**
|
||||
* Retrieves the port which this event is bound to.
|
||||
* Retrieves the port which this listener is bound to.
|
||||
*/
|
||||
unsigned getPort() const { return (m_Port); }
|
||||
unsigned getPort() const { return m_Port; }
|
||||
/**
|
||||
* Sets the port which this event is bound to.
|
||||
* Sets the port which this listener is bound to.
|
||||
*/
|
||||
void setPort(unsigned port) { m_Port = port; }
|
||||
/**
|
||||
* Checks whether a given port number is matching.
|
||||
* @param p The port number an I/O event occured on
|
||||
* @param p The port number an I/O listener occured on
|
||||
* @param out True if the communication was outbound, false otherwise
|
||||
*/
|
||||
bool isMatching(unsigned p, bool out) const { return ( out = isOutEvent() && p == getPort()); }
|
||||
bool isMatching(unsigned p, bool out) const { return ( out = isOutListener() && p == getPort()); }
|
||||
/**
|
||||
* Tells you if this event is capturing outbound communication (inbound if false)
|
||||
* Tells you if this listener is capturing outbound communication (inbound if false)
|
||||
*/
|
||||
bool isOutEvent() const { return m_Out; }
|
||||
bool isOutListener() const { return m_Out; }
|
||||
/**
|
||||
* Change the event direction.
|
||||
* Change the listener direction.
|
||||
* \arg \c true Output on the given port is captured.
|
||||
* \arg \c false Input on the given port is captured.
|
||||
*/
|
||||
@ -524,36 +535,36 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* \class JumpEvent
|
||||
* JumpEvents are used to observe conditional jumps (if...else if...else).
|
||||
* \class JumpListener
|
||||
* JumpListeners are used to observe conditional jumps (if...else if...else).
|
||||
*/
|
||||
class JumpEvent : virtual public BaseEvent {
|
||||
class JumpListener : virtual public BaseListener {
|
||||
private:
|
||||
unsigned m_Opcode;
|
||||
bool m_FlagTriggered;
|
||||
public:
|
||||
/**
|
||||
* Constructs a new event object.
|
||||
* Constructs a new listener object.
|
||||
* @param parent the parent object
|
||||
* @param opcode the opcode of the jump-instruction to be observed
|
||||
* or ANY_INSTR to match all jump-instructions
|
||||
*/
|
||||
JumpEvent(unsigned opcode = ANY_INSTR)
|
||||
JumpListener(unsigned opcode = ANY_INSTR)
|
||||
: m_Opcode(opcode), m_FlagTriggered(false) { }
|
||||
/**
|
||||
* Retrieves the opcode of the jump-instruction.
|
||||
*/
|
||||
unsigned getOpcode() const { return (m_Opcode); }
|
||||
unsigned getOpcode() const { return m_Opcode; }
|
||||
/**
|
||||
* Returns \c true, if the event was triggered due to specific register
|
||||
* Returns \c true, if the listener was triggered due to specific register
|
||||
* content, \c false otherwise.
|
||||
*/
|
||||
bool isRegisterTriggered() { return (!m_FlagTriggered); }
|
||||
bool isRegisterTriggered() { return !m_FlagTriggered; }
|
||||
/**
|
||||
* Returns \c true, of the event was triggered due to specific FLAG
|
||||
* Returns \c true, of the listener was triggered due to specific FLAG
|
||||
* state, \c false otherwise. This is the common case.
|
||||
*/
|
||||
bool isFlagTriggered() { return (m_FlagTriggered); }
|
||||
bool isFlagTriggered() { return m_FlagTriggered; }
|
||||
/**
|
||||
* Sets the requestet jump-instruction opcode.
|
||||
*/
|
||||
@ -561,36 +572,35 @@ public:
|
||||
/**
|
||||
* Sets the trigger flag.
|
||||
*/
|
||||
void setFlagTriggered(bool flagTriggered)
|
||||
{ m_FlagTriggered = flagTriggered; }
|
||||
void setFlagTriggered(bool flagTriggered) { m_FlagTriggered = flagTriggered; }
|
||||
};
|
||||
|
||||
/**
|
||||
* \class GenericTimerEvent
|
||||
* This event type is used to create timeouts within in an experiment.
|
||||
* \class GenericTimerListener
|
||||
* This listener type is used to create timeouts within in an experiment.
|
||||
*
|
||||
* Depending on your simulator backend, a concrete class needs to be derived
|
||||
* from \c GenericTimerEvent. \c onEventAddition should be used to register and
|
||||
* \c onEventDeletion to unregister the timer. \c onEventTrigger can be used to
|
||||
* from \c GenericTimerListener. \c onListenerAddition should be used to register and
|
||||
* \c onListenerDeletion to unregister the timer. \c onListenerTrigger can be used to
|
||||
* re-register the timer if a repetitive timer is requested and the back-
|
||||
* end doesn't support such timer types natively.
|
||||
*/
|
||||
class GenericTimerEvent : public BaseEvent {
|
||||
class GenericTimerListener : public BaseListener {
|
||||
protected:
|
||||
unsigned m_Timeout; //!< timeout interval in milliseconds
|
||||
timer_id_t m_Id; //!< internal timer id (sim-specific)
|
||||
bool m_Once; //!< \c true, if the timer should be triggered only once, \c false otherwise
|
||||
public:
|
||||
/**
|
||||
* Creates a new timer event. This can be used to implement a timeout-
|
||||
* Creates a new timer listener. This can be used to implement a timeout-
|
||||
* mechanism in the experiment-flow. The timer starts automatically when
|
||||
* added to the simulator backend.
|
||||
* @param timeout the time intervall in milliseconds (ms)
|
||||
* @see SimulatorController::addEvent
|
||||
* @see SimulatorController::addListener
|
||||
*/
|
||||
GenericTimerEvent(unsigned timeout)
|
||||
GenericTimerListener(unsigned timeout)
|
||||
: m_Timeout(timeout), m_Id(-1) { }
|
||||
~GenericTimerEvent() { }
|
||||
~GenericTimerListener() { }
|
||||
/**
|
||||
* Retrieves the internal timer id. Maybe useful for debug output.
|
||||
* @return the timer id
|
||||
@ -610,4 +620,4 @@ public:
|
||||
|
||||
} // end-of-namespace: fail
|
||||
|
||||
#endif // __EVENT_HPP__
|
||||
#endif // __LISTENER_HPP__
|
||||
191
src/core/sal/ListenerManager.cc
Normal file
191
src/core/sal/ListenerManager.cc
Normal file
@ -0,0 +1,191 @@
|
||||
#include <set>
|
||||
|
||||
#include "ListenerManager.hpp"
|
||||
#include "SALInst.hpp"
|
||||
|
||||
namespace fail {
|
||||
|
||||
void ListenerManager::addToCaches(BaseListener *li)
|
||||
{
|
||||
BPListener *bps_li;
|
||||
if ((bps_li = dynamic_cast<BPListener*>(li)) != NULL)
|
||||
m_Bp_cache.add(bps_li);
|
||||
|
||||
IOPortListener *io_li;
|
||||
if ((io_li = dynamic_cast<IOPortListener*>(li)) != NULL)
|
||||
m_Io_cache.add(io_li);
|
||||
}
|
||||
|
||||
void ListenerManager::removeFromCaches(BaseListener *li)
|
||||
{
|
||||
BPListener *bpr_li;
|
||||
if ((bpr_li = dynamic_cast<BPListener*>(li)) != NULL)
|
||||
m_Bp_cache.remove(bpr_li);
|
||||
|
||||
IOPortListener *io_li;
|
||||
if ((io_li = dynamic_cast<IOPortListener*>(li)) != NULL)
|
||||
m_Io_cache.remove(io_li);
|
||||
}
|
||||
|
||||
void ListenerManager::clearCaches()
|
||||
{
|
||||
m_Bp_cache.clear();
|
||||
m_Io_cache.clear();
|
||||
}
|
||||
|
||||
void ListenerManager::add(BaseListener* li, ExperimentFlow* pExp)
|
||||
{
|
||||
assert(li != NULL && "FATAL ERROR: Listener (of base type BaseListener*) cannot be NULL!");
|
||||
// a zero counter does not make sense
|
||||
assert(li->getCounter() != 0);
|
||||
li->setParent(pExp); // listener is linked to experiment flow
|
||||
|
||||
addToCaches(li);
|
||||
m_BufferList.push_back(li);
|
||||
}
|
||||
|
||||
void ListenerManager::remove(BaseListener* li)
|
||||
{
|
||||
// possible cases:
|
||||
// - li == 0 -> remove all listeners
|
||||
// * clear m_BufferList
|
||||
// * copy m_FireList to m_DeleteList
|
||||
if (li == 0) {
|
||||
for (bufferlist_t::iterator it = m_BufferList.begin(); it != m_BufferList.end(); it++)
|
||||
(*it)->onDeletion();
|
||||
for (firelist_t::iterator it = m_FireList.begin(); it != m_FireList.end(); it++)
|
||||
(*it)->onDeletion();
|
||||
clearCaches();
|
||||
m_BufferList.clear();
|
||||
// all remaining active listeners must not fire anymore
|
||||
m_DeleteList.insert(m_DeleteList.end(), m_FireList.begin(), m_FireList.end());
|
||||
|
||||
// - li != 0 -> remove single listener
|
||||
// * find/remove 'li' in 'm_BufferList'
|
||||
// * if 'li' in 'm_FireList', copy to 'm_DeleteList'
|
||||
} else {
|
||||
li->onDeletion();
|
||||
|
||||
removeFromCaches(li);
|
||||
m_BufferList.remove(li);
|
||||
firelist_t::const_iterator it =
|
||||
std::find(m_FireList.begin(), m_FireList.end(), li);
|
||||
if (it != m_FireList.end()) {
|
||||
m_DeleteList.push_back(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListenerManager::iterator ListenerManager::m_remove(iterator it, bool skip_deletelist)
|
||||
{
|
||||
if (!skip_deletelist) {
|
||||
// If skip_deletelist = true, m_remove was called from makeActive. Accordingly, we
|
||||
// are not going to delete an listener, instead we are "moving" a listener object (= *it)
|
||||
// from the buffer list to the fire-list. Therefore we only need to call the simulator's
|
||||
// listener handler (onDeletion), if m_remove is called with the primary intention
|
||||
// to *delete* (not "move") a listener.
|
||||
(*it)->onDeletion();
|
||||
m_DeleteList.push_back(*it);
|
||||
|
||||
// Cached listeners have their own BufferCache<T>::makeActive() implementation, which
|
||||
// calls this method and afterwards erase() in the cache class. This is why, when
|
||||
// called from any kind of makeActive() method, it is unnecessary to call
|
||||
// BufferCache<T>::remove() from m_remove().
|
||||
|
||||
// NOTE: in case the semantics of skip_deletelist change, please adapt the following lines
|
||||
removeFromCaches(*it);
|
||||
}
|
||||
|
||||
return m_BufferList.erase(it);
|
||||
}
|
||||
|
||||
void ListenerManager::remove(ExperimentFlow* flow)
|
||||
{
|
||||
// all listeners?
|
||||
if (flow == 0) {
|
||||
for (bufferlist_t::iterator it = m_BufferList.begin();
|
||||
it != m_BufferList.end(); it++)
|
||||
(*it)->onDeletion(); // invoke listener handler
|
||||
clearCaches();
|
||||
m_BufferList.clear();
|
||||
} else { // remove all listeners corresponding to a specific experiment ("flow"):
|
||||
for (bufferlist_t::iterator it = m_BufferList.begin();
|
||||
it != m_BufferList.end(); ) {
|
||||
if ((*it)->getParent() == flow) {
|
||||
(*it)->onDeletion();
|
||||
it = m_BufferList.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
// listeners that just fired / are about to fire ...
|
||||
for (firelist_t::const_iterator it = m_FireList.begin();
|
||||
it != m_FireList.end(); it++) {
|
||||
if (std::find(m_DeleteList.begin(), m_DeleteList.end(), *it)
|
||||
!= m_DeleteList.end()) {
|
||||
continue; // (already in the delete-list? -> skip!)
|
||||
}
|
||||
// ... need to be pushed into m_DeleteList, as we're currently
|
||||
// iterating over m_FireList in triggerActiveListeners() and cannot modify it
|
||||
if (flow == 0 || (*it)->getParent() == flow) {
|
||||
(*it)->onDeletion();
|
||||
m_DeleteList.push_back(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListenerManager::~ListenerManager()
|
||||
{
|
||||
// nothing to do here yet
|
||||
}
|
||||
|
||||
ListenerManager::iterator ListenerManager::makeActive(iterator it)
|
||||
{
|
||||
assert(it != m_BufferList.end() &&
|
||||
"FATAL ERROR: Iterator has already reached the end!");
|
||||
BaseListener* li = *it;
|
||||
assert(li && "FATAL ERROR: Listener object pointer cannot be NULL!");
|
||||
li->decreaseCounter();
|
||||
if (li->getCounter() > 0) {
|
||||
return ++it;
|
||||
}
|
||||
li->resetCounter();
|
||||
// Note: This is the one and only situation in which remove() should NOT
|
||||
// store the removed item in the delete-list.
|
||||
iterator it_next = m_remove(it, true); // remove listener from buffer-list
|
||||
m_FireList.push_back(li);
|
||||
return it_next;
|
||||
}
|
||||
|
||||
void ListenerManager::triggerActiveListeners()
|
||||
{
|
||||
for (firelist_t::iterator it = m_FireList.begin();
|
||||
it != m_FireList.end(); it++) {
|
||||
if (std::find(m_DeleteList.begin(), m_DeleteList.end(), *it)
|
||||
== m_DeleteList.end()) { // not found in delete-list?
|
||||
m_pFired = *it;
|
||||
// Inform (call) the simulator's (internal) listener handler that we are about
|
||||
// to trigger an listener (*before* we actually toggle the experiment flow):
|
||||
m_pFired->onTrigger();
|
||||
ExperimentFlow* pFlow = m_pFired->getParent();
|
||||
assert(pFlow && "FATAL ERROR: The listener has no parent experiment (owner)!");
|
||||
simulator.m_Flows.toggle(pFlow);
|
||||
}
|
||||
}
|
||||
m_FireList.clear();
|
||||
m_DeleteList.clear();
|
||||
// Note: Do NOT call any listener handlers here!
|
||||
}
|
||||
|
||||
size_t ListenerManager::getContextCount() const
|
||||
{
|
||||
std::set<ExperimentFlow*> uniqueFlows; // count unique ExperimentFlow-ptr
|
||||
for (bufferlist_t::const_iterator it = m_BufferList.begin();
|
||||
it != m_BufferList.end(); it++)
|
||||
uniqueFlows.insert((*it)->getParent());
|
||||
|
||||
return uniqueFlows.size();
|
||||
}
|
||||
|
||||
} // end-of-namespace: fail
|
||||
219
src/core/sal/ListenerManager.hpp
Normal file
219
src/core/sal/ListenerManager.hpp
Normal file
@ -0,0 +1,219 @@
|
||||
#ifndef __LISTENER_MANAGER_HPP__
|
||||
#define __LISTENER_MANAGER_HPP__
|
||||
|
||||
#include <cassert>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Listener.hpp"
|
||||
#include "BufferCache.hpp"
|
||||
|
||||
namespace fail {
|
||||
|
||||
class ExperimentFlow;
|
||||
|
||||
/**
|
||||
* Buffer-list for a specific experiment; acts as a simple storage container
|
||||
* for listeners to watch for:
|
||||
*/
|
||||
typedef std::list<BaseListener*> bufferlist_t;
|
||||
/**
|
||||
* List of listeners that match the current simulator listener; these listeners will
|
||||
* be triggered next (the list is used temporarily).
|
||||
*/
|
||||
typedef std::vector<BaseListener*> firelist_t;
|
||||
/**
|
||||
* List of listeners that have been deleted during a toggled experiment flow while
|
||||
* triggering the listeners in the fire-list. This list is used to skip already
|
||||
* deleted listeners (therefore it is used temporarily either).
|
||||
*/
|
||||
typedef std::vector<BaseListener*> deletelist_t;
|
||||
|
||||
/**
|
||||
* Cache classes for the most commonly used types of listeners, utilising static typing.
|
||||
* Apart from that, they work like bufferlist_t.
|
||||
*/
|
||||
typedef BufferCache<BPListener*> bp_cache_t;
|
||||
typedef bp_cache_t::iterator bp_iter_t;
|
||||
typedef BufferCache<IOPortListener*> io_cache_t;
|
||||
typedef io_cache_t::iterator io_iter_t;
|
||||
/**
|
||||
* \class ListenerManager
|
||||
*
|
||||
* \brief This class manages the listeners of the Fail* implementation.
|
||||
*
|
||||
* If a listener is triggered, the internal data structure will be updated (i.e.,
|
||||
* the listener will be removed from the so called buffer-list and added to the
|
||||
* fire-list). Additionally, if an experiment-flow deletes an "active" listener
|
||||
* which is currently stored in the fire-list, the listener (to be removed) will
|
||||
* be added to a -so called- delete-list. This ensures to prevent triggering
|
||||
* "active" listeners which have already been deleted by a previous experiment
|
||||
* flow. (See makeActive() and fireActiveListener() for implementation specific
|
||||
* details.) ListenerManager is part of the SimulatorController and "outsources"
|
||||
* it's listener management.
|
||||
*/
|
||||
class ListenerManager {
|
||||
private:
|
||||
// TODO: List separation of "critical types"? Hashing/sorted lists? (-> performance!)
|
||||
bufferlist_t m_BufferList; //!< the storage for listeners added by exp.
|
||||
firelist_t m_FireList; //!< the active listeners (used temporarily)
|
||||
deletelist_t m_DeleteList; //!< the deleted listeners (used temporarily)
|
||||
BaseListener* m_pFired; //!< the recently fired Listener-object
|
||||
bp_cache_t m_Bp_cache; //!< the storage cache for breakpoint listeners
|
||||
io_cache_t m_Io_cache; //!< the storage cache for port i/o listeners
|
||||
friend bp_iter_t bp_cache_t::makeActive(ListenerManager &ev_list, bp_iter_t idx);
|
||||
friend io_iter_t io_cache_t::makeActive(ListenerManager &ev_list, io_iter_t idx);
|
||||
public:
|
||||
/**
|
||||
* The iterator of this class used to loop through the list of added
|
||||
* listeners. To retrieve an iterator to the first element, call \c begin().
|
||||
* \c end() returns the iterator, pointing after the last element.
|
||||
* (This behaviour equals the STL iterator in C++.)
|
||||
*/
|
||||
typedef bufferlist_t::iterator iterator;
|
||||
|
||||
ListenerManager() : m_pFired(NULL) { }
|
||||
~ListenerManager();
|
||||
/**
|
||||
* Adds the specified listener object for the given ExperimentFlow to the
|
||||
* list of listeners to be watched for.
|
||||
* @param li pointer to the listener object to be added (cannot be \c NULL)
|
||||
* @param flow the listener context (a pointer to the experiment object
|
||||
* which is interested in such listeners (cannot be \c NULL)
|
||||
* @return the id of the added listener object, that is ev->getId()
|
||||
*/
|
||||
void add(BaseListener* li, ExperimentFlow* flow);
|
||||
/**
|
||||
* Removes the listener based upon the specified \a ev pointer (requires
|
||||
* to loop through the whole buffer-list).
|
||||
* @param li the pointer of the listener to be removed; if ev is set to
|
||||
* \c NULL, all listeners (for \a all experiments) will be
|
||||
* removed
|
||||
*/
|
||||
void remove(BaseListener* li);
|
||||
/**
|
||||
* Behaves like remove(BaseListener*) and additionally updates the provided
|
||||
* iterator.
|
||||
* @param it the iterator pointing to the Listener object to be removed
|
||||
* @return the updated iterator which will point to the next element
|
||||
*/
|
||||
iterator remove(iterator it);
|
||||
private:
|
||||
/**
|
||||
* Internal implementation of remove(iterator it) that allows
|
||||
* to skip the delete-list.
|
||||
* @param it the iterator pointing to the Listener object to be removed
|
||||
* @param skip_deletelist \c true to skip the deletion of the Listener object
|
||||
* or \false to behave like \c remove(iterator)
|
||||
* @return the updated iterator which will point to the next element
|
||||
*/
|
||||
iterator m_remove(iterator it, bool skip_deletelist);
|
||||
public:
|
||||
/**
|
||||
* Returns an iterator to the beginning of the internal data structure.
|
||||
* Don't forget to update the returned iterator when calling one of the
|
||||
* modifying methods like makeActive() or remove(). Therefore you need
|
||||
* to call the iterator-based variants of makeActive() and remove().
|
||||
* \code
|
||||
* [X|1|2| ... |n]
|
||||
* ^
|
||||
* \endcode
|
||||
* @return iterator to the beginning
|
||||
*/
|
||||
iterator begin() { return (m_BufferList.begin()); }
|
||||
/**
|
||||
* Returns an iterator to the end of the interal data structure.
|
||||
* Don't forget to update the returned iterator when calling one of the
|
||||
* modifying methods like makeActive() or remove(). Therefore you need
|
||||
* to call the iterator-based variants of makeActive() and remove().
|
||||
* \code
|
||||
* [1|2| ... |n]X
|
||||
* ^
|
||||
* \endcode
|
||||
* @return iterator to the end
|
||||
*/
|
||||
iterator end() { return (m_BufferList.end()); }
|
||||
/**
|
||||
* Removes all listeners for the specified experiment.
|
||||
* @param flow pointer to experiment context (0 = all experiments)
|
||||
*/
|
||||
void remove(ExperimentFlow* flow);
|
||||
/**
|
||||
* Retrieves the number of experiments which currently have active
|
||||
* listeners. This number is trivially equal to the (current) total
|
||||
* number of ExperimentFlow-objects.
|
||||
* @return number of experiments having active listeners
|
||||
*/
|
||||
size_t getContextCount() const;
|
||||
/**
|
||||
* Retrieves the total number of buffered listeners. This doesn't include
|
||||
* the listeners in the fire- or delete-list.
|
||||
* @return the total listener count (for all flows)
|
||||
*/
|
||||
size_t getListenerCount() const { return m_BufferList.size(); }
|
||||
/**
|
||||
* Retrieves the recently triggered listener object. To map this object to it's
|
||||
* context (i.e., the related \c ExerimentFlow), use \c getLastFiredDest().
|
||||
* @return a pointer to the recent listener or \c NULL if nothing has been
|
||||
* triggered so far
|
||||
*/
|
||||
BaseListener* getLastFired() { return (m_pFired); }
|
||||
/**
|
||||
* Retrieves the ExperimentFlow-object for the given BaseListener (it's
|
||||
* \a context).
|
||||
* @param li the listener object to be looked up
|
||||
* @return a pointer to the context of \a pEv or \c NULL if the
|
||||
* corresponding context could not be found
|
||||
*/
|
||||
ExperimentFlow* getExperimentOf(BaseListener* li);
|
||||
/**
|
||||
* Moves the listeners from the (internal) buffer-list to the fire-list.
|
||||
* To actually fire the listeners, call triggerActiveListeners().
|
||||
* Returns an updated iterator which points to the next element.
|
||||
* @param ev the listener to trigger
|
||||
* @return returns the updated iteration, pointing to the next element
|
||||
* after makeActive returns, "it" is invalid, so the returned
|
||||
* iterator should be used to continue the iteration
|
||||
*
|
||||
* TODO: Improve naming (instead of "makeActive")?
|
||||
*/
|
||||
iterator makeActive(iterator it);
|
||||
/**
|
||||
* Triggers the active listeners. Each listener is triggered if it has not
|
||||
* recently been removed (id est: is not found in the delete-list). See
|
||||
* makeActive() for more details. The recently triggered listener can be
|
||||
* retrieved by calling \a getLastFired(). After all listeners have been
|
||||
* triggered, the (internal) fire- and delete-list will be cleared.
|
||||
*/
|
||||
void triggerActiveListeners();
|
||||
/**
|
||||
* Retrieves the BPListener buffer cache.
|
||||
* @returns the buffer cache
|
||||
*/
|
||||
inline bp_cache_t &getBPBuffer() { return m_Bp_cache; }
|
||||
/**
|
||||
* Retrieves the IOPortListener buffer cache.
|
||||
* @returns the buffer cache
|
||||
*/
|
||||
inline io_cache_t &getIOBuffer() { return m_Io_cache; }
|
||||
private:
|
||||
/**
|
||||
* Add an listener to its appropriate cache.
|
||||
* @param li the listener to add
|
||||
*/
|
||||
void addToCaches(BaseListener* li);
|
||||
/**
|
||||
* Remove an listener from its cache.
|
||||
* @param li the listener to remove
|
||||
*/
|
||||
void removeFromCaches(BaseListener* li);
|
||||
/**
|
||||
* Clear the listener caches.
|
||||
*/
|
||||
void clearCaches();
|
||||
};
|
||||
|
||||
} // end-of-namespace: fail
|
||||
|
||||
#endif // __LISTENER_MANAGER_HPP__
|
||||
@ -55,12 +55,12 @@ public:
|
||||
* Returns the (fixed) type of this register.
|
||||
* @return the type of this register
|
||||
*/
|
||||
RegisterType getType() const { return (m_Type); }
|
||||
RegisterType getType() const { return m_Type; }
|
||||
/**
|
||||
* Returns the (fixed) width of this register.
|
||||
* @return the width in bits
|
||||
*/
|
||||
regwidth_t getWidth() const { return (m_Width); }
|
||||
regwidth_t getWidth() const { return m_Width; }
|
||||
/**
|
||||
* Returns the data referenced by this register. In a concrete
|
||||
* derived class this method has to be defined appropriately.
|
||||
@ -81,7 +81,7 @@ public:
|
||||
* Retrieves the register name.
|
||||
* @return the textual register description
|
||||
*/
|
||||
const std::string& getName() const { return (m_Name); }
|
||||
const std::string& getName() const { return m_Name; }
|
||||
/**
|
||||
* Retrieves the unique index within it's assigned register set.
|
||||
* If the register has not been assigned, \c (size_t)-1 will be
|
||||
@ -89,18 +89,18 @@ public:
|
||||
* @return the register index or -1 if not assigned
|
||||
* @see isAssigned()
|
||||
*/
|
||||
size_t getIndex() const { return (m_Index); }
|
||||
size_t getIndex() const { return m_Index; }
|
||||
/**
|
||||
* Checks whether this register has already been assigned. On
|
||||
* creation the register isn't initially assigned.
|
||||
* @return \c true if assigned, \c false otherwise
|
||||
*/
|
||||
bool isAssigned() const { return (m_Assigned); }
|
||||
bool isAssigned() const { return m_Assigned; }
|
||||
/**
|
||||
* Returns the unique id of this register.
|
||||
* @return the unique id
|
||||
*/
|
||||
unsigned int getId() const { return (m_Id); }
|
||||
unsigned int getId() const { return m_Id; }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -132,7 +132,7 @@ public:
|
||||
* ^
|
||||
* \endcode
|
||||
*/
|
||||
iterator begin() { return (m_Regs.begin()); }
|
||||
iterator begin() { return m_Regs.begin(); }
|
||||
/**
|
||||
* Returns an iterator to the end of the interal data structure.
|
||||
* \code
|
||||
@ -140,7 +140,7 @@ public:
|
||||
* ^
|
||||
* \endcode
|
||||
*/
|
||||
iterator end() { return (m_Regs.end()); }
|
||||
iterator end() { return m_Regs.end(); }
|
||||
/**
|
||||
* Constructs a new register set with type \a containerType.
|
||||
* @param containerType the type of registers which should be stored
|
||||
@ -152,12 +152,12 @@ public:
|
||||
* Returns the type of this set.
|
||||
* @return the type
|
||||
*/
|
||||
RegisterType getType() const { return (m_Type); }
|
||||
RegisterType getType() const { return m_Type; }
|
||||
/**
|
||||
* Gets the number of registers of this set.
|
||||
* @return the number of registers
|
||||
*/
|
||||
size_t count() const { return (m_Regs.size()); }
|
||||
size_t count() const { return m_Regs.size(); }
|
||||
/**
|
||||
* Retrieves the \a i-th register within this set.
|
||||
* @return a pointer to the \a i-th register; if \a i is invalid, an
|
||||
@ -169,7 +169,7 @@ public:
|
||||
* @return a pointer to the first register (if existing -- otherwise an
|
||||
* assertion is thrown)
|
||||
*/
|
||||
virtual Register* first() { return (getRegister(0)); }
|
||||
virtual Register* first() { return getRegister(0); }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -197,7 +197,7 @@ public:
|
||||
* ^
|
||||
* \endcode
|
||||
*/
|
||||
iterator begin() { return (m_Registers.begin()); }
|
||||
iterator begin() { return m_Registers.begin(); }
|
||||
/**
|
||||
* Returns an iterator to the end of the interal data structure.
|
||||
* \code
|
||||
@ -205,7 +205,7 @@ public:
|
||||
* ^
|
||||
* \endcode
|
||||
*/
|
||||
iterator end() { return (m_Registers.end()); }
|
||||
iterator end() { return m_Registers.end(); }
|
||||
|
||||
RegisterManager() { }
|
||||
~RegisterManager() { clear(); }
|
||||
@ -218,7 +218,7 @@ public:
|
||||
* Retrieves the number of managed homogeneous register sets.
|
||||
* @return the number of sets
|
||||
*/
|
||||
virtual size_t subsetCount() const { return (m_Subsets.size()); }
|
||||
virtual size_t subsetCount() const { return m_Subsets.size(); }
|
||||
/**
|
||||
* Gets the \a i-th register set.
|
||||
* @param i the index of the set to be returned
|
||||
|
||||
@ -6,26 +6,26 @@ namespace fail {
|
||||
// External reference declared in SALInst.hpp
|
||||
ConcreteSimulatorController simulator;
|
||||
|
||||
bool SimulatorController::addEvent(BaseEvent* ev)
|
||||
bool SimulatorController::addListener(BaseListener* li)
|
||||
{
|
||||
assert(ev != NULL && "FATAL ERROR: Argument (ptr) cannot be NULL!");
|
||||
m_EvList.add(ev, m_Flows.getCurrent());
|
||||
assert(li != NULL && "FATAL ERROR: Argument (ptr) cannot be NULL!");
|
||||
m_LstList.add(li, m_Flows.getCurrent());
|
||||
// Call the common postprocessing function:
|
||||
if (!ev->onEventAddition()) { // If the return value signals "false"...,
|
||||
m_EvList.remove(ev); // ...skip the addition
|
||||
if (!li->onAddition()) { // If the return value signals "false"...,
|
||||
m_LstList.remove(li); // ...skip the addition
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BaseEvent* SimulatorController::waitAny(void)
|
||||
BaseListener* SimulatorController::resume(void)
|
||||
{
|
||||
if (!hasEvents())
|
||||
if (!hasListeners())
|
||||
return NULL;
|
||||
m_Flows.resume();
|
||||
assert(m_EvList.getLastFired() != NULL &&
|
||||
assert(m_LstList.getLastFired() != NULL &&
|
||||
"FATAL ERROR: getLastFired() expected to be non-NULL!");
|
||||
return m_EvList.getLastFired();
|
||||
return m_LstList.getLastFired();
|
||||
}
|
||||
|
||||
void SimulatorController::startup()
|
||||
@ -43,47 +43,47 @@ void SimulatorController::initExperiments()
|
||||
/* empty. */
|
||||
}
|
||||
|
||||
void SimulatorController::onBreakpointEvent(address_t instrPtr, address_t address_space)
|
||||
void SimulatorController::onBreakpointListener(address_t instrPtr, address_t address_space)
|
||||
{
|
||||
assert(false &&
|
||||
"FIXME: SimulatorController::onBreakpointEvent() has not been tested before");
|
||||
"FIXME: SimulatorController::onBreakpointListener() has not been tested before");
|
||||
// FIXME: Improve performance!
|
||||
|
||||
// Loop through all events of type BP*Event:
|
||||
EventManager::iterator it = m_EvList.begin();
|
||||
while (it != m_EvList.end()) {
|
||||
BaseEvent* pev = *it;
|
||||
BPSingleEvent* pbp; BPRangeEvent* pbpr;
|
||||
if ((pbp = dynamic_cast<BPSingleEvent*>(pev)) && pbp->isMatching(instrPtr, address_space)) {
|
||||
// Loop through all events of type BP*Listener:
|
||||
ListenerManager::iterator it = m_LstList.begin();
|
||||
while (it != m_LstList.end()) {
|
||||
BaseListener* pev = *it;
|
||||
BPSingleListener* pbp; BPRangeListener* pbpr;
|
||||
if ((pbp = dynamic_cast<BPSingleListener*>(pev)) && pbp->isMatching(instrPtr, address_space)) {
|
||||
pbp->setTriggerInstructionPointer(instrPtr);
|
||||
it = m_EvList.makeActive(it);
|
||||
it = m_LstList.makeActive(it);
|
||||
// "it" has already been set to the next element (by calling
|
||||
// makeActive()):
|
||||
continue; // -> skip iterator increment
|
||||
} else if ((pbpr = dynamic_cast<BPRangeEvent*>(pev)) &&
|
||||
} else if ((pbpr = dynamic_cast<BPRangeListener*>(pev)) &&
|
||||
pbpr->isMatching(instrPtr, address_space)) {
|
||||
pbpr->setTriggerInstructionPointer(instrPtr);
|
||||
it = m_EvList.makeActive(it);
|
||||
it = m_LstList.makeActive(it);
|
||||
continue; // dito
|
||||
}
|
||||
++it;
|
||||
}
|
||||
m_EvList.fireActiveEvents();
|
||||
m_LstList.triggerActiveListeners();
|
||||
}
|
||||
|
||||
void SimulatorController::onMemoryAccessEvent(address_t addr, size_t len,
|
||||
void SimulatorController::onMemoryAccessListener(address_t addr, size_t len,
|
||||
bool is_write, address_t instrPtr)
|
||||
{
|
||||
// FIXME: Improve performance!
|
||||
MemAccessEvent::accessType_t accesstype =
|
||||
is_write ? MemAccessEvent::MEM_WRITE
|
||||
: MemAccessEvent::MEM_READ;
|
||||
MemAccessListener::accessType_t accesstype =
|
||||
is_write ? MemAccessListener::MEM_WRITE
|
||||
: MemAccessListener::MEM_READ;
|
||||
|
||||
EventManager::iterator it = m_EvList.begin();
|
||||
while (it != m_EvList.end()) { // check for active events
|
||||
BaseEvent* pev = *it;
|
||||
MemAccessEvent* ev = dynamic_cast<MemAccessEvent*>(pev);
|
||||
// Is this a MemAccessEvent? Correct access type?
|
||||
ListenerManager::iterator it = m_LstList.begin();
|
||||
while (it != m_LstList.end()) { // check for active events
|
||||
BaseListener* pev = *it;
|
||||
MemAccessListener* ev = dynamic_cast<MemAccessListener*>(pev);
|
||||
// Is this a MemAccessListener? Correct access type?
|
||||
if (!ev || !ev->isMatching(addr, accesstype)) {
|
||||
++it;
|
||||
continue; // skip event activation
|
||||
@ -92,26 +92,26 @@ void SimulatorController::onMemoryAccessEvent(address_t addr, size_t len,
|
||||
ev->setTriggerWidth(len);
|
||||
ev->setTriggerInstructionPointer(instrPtr);
|
||||
ev->setTriggerAccessType(accesstype);
|
||||
it = m_EvList.makeActive(it);
|
||||
it = m_LstList.makeActive(it);
|
||||
}
|
||||
m_EvList.fireActiveEvents();
|
||||
m_LstList.triggerActiveListeners();
|
||||
}
|
||||
|
||||
void SimulatorController::onInterruptEvent(unsigned interruptNum, bool nmi)
|
||||
void SimulatorController::onInterruptListener(unsigned interruptNum, bool nmi)
|
||||
{
|
||||
EventManager::iterator it = m_EvList.begin();
|
||||
while (it != m_EvList.end()) { // check for active events
|
||||
BaseEvent* pev = *it;
|
||||
InterruptEvent* pie = dynamic_cast<InterruptEvent*>(pev);
|
||||
ListenerManager::iterator it = m_LstList.begin();
|
||||
while (it != m_LstList.end()) { // check for active events
|
||||
BaseListener* pev = *it;
|
||||
InterruptListener* pie = dynamic_cast<InterruptListener*>(pev);
|
||||
if (!pie || !pie->isMatching(interruptNum)) {
|
||||
++it;
|
||||
continue; // skip event activation
|
||||
}
|
||||
pie->setTriggerNumber(interruptNum);
|
||||
pie->setNMI(nmi);
|
||||
it = m_EvList.makeActive(it);
|
||||
it = m_LstList.makeActive(it);
|
||||
}
|
||||
m_EvList.fireActiveEvents();
|
||||
m_LstList.triggerActiveListeners();
|
||||
}
|
||||
|
||||
bool SimulatorController::isSuppressedInterrupt(unsigned interruptNum)
|
||||
@ -155,53 +155,53 @@ bool SimulatorController::removeSuppressedInterrupt(unsigned interruptNum)
|
||||
return false;
|
||||
}
|
||||
|
||||
void SimulatorController::onTrapEvent(unsigned trapNum)
|
||||
void SimulatorController::onTrapListener(unsigned trapNum)
|
||||
{
|
||||
EventManager::iterator it = m_EvList.begin();
|
||||
while (it != m_EvList.end()) { // check for active events
|
||||
BaseEvent* pev = *it;
|
||||
TrapEvent* pte = dynamic_cast<TrapEvent*>(pev);
|
||||
ListenerManager::iterator it = m_LstList.begin();
|
||||
while (it != m_LstList.end()) { // check for active events
|
||||
BaseListener* pev = *it;
|
||||
TrapListener* pte = dynamic_cast<TrapListener*>(pev);
|
||||
if (!pte || !pte->isMatching(trapNum)) {
|
||||
++it;
|
||||
continue; // skip event activation
|
||||
}
|
||||
pte->setTriggerNumber(trapNum);
|
||||
it = m_EvList.makeActive(it);
|
||||
it = m_LstList.makeActive(it);
|
||||
}
|
||||
m_EvList.fireActiveEvents();
|
||||
m_LstList.triggerActiveListeners();
|
||||
}
|
||||
|
||||
void SimulatorController::onGuestSystemEvent(char data, unsigned port)
|
||||
void SimulatorController::onGuestSystemListener(char data, unsigned port)
|
||||
{
|
||||
EventManager::iterator it = m_EvList.begin();
|
||||
while (it != m_EvList.end()) { // check for active events
|
||||
BaseEvent* pev = *it;
|
||||
GuestEvent* pge = dynamic_cast<GuestEvent*>(pev);
|
||||
ListenerManager::iterator it = m_LstList.begin();
|
||||
while (it != m_LstList.end()) { // check for active events
|
||||
BaseListener* pev = *it;
|
||||
GuestListener* pge = dynamic_cast<GuestListener*>(pev);
|
||||
if (pge != NULL) {
|
||||
pge->setData(data);
|
||||
pge->setPort(port);
|
||||
it = m_EvList.makeActive(it);
|
||||
it = m_LstList.makeActive(it);
|
||||
continue; // dito.
|
||||
}
|
||||
++it;
|
||||
}
|
||||
m_EvList.fireActiveEvents();
|
||||
m_LstList.triggerActiveListeners();
|
||||
}
|
||||
|
||||
void SimulatorController::onJumpEvent(bool flagTriggered, unsigned opcode)
|
||||
void SimulatorController::onJumpListener(bool flagTriggered, unsigned opcode)
|
||||
{
|
||||
EventManager::iterator it = m_EvList.begin();
|
||||
while (it != m_EvList.end()) { // check for active events
|
||||
JumpEvent* pje = dynamic_cast<JumpEvent*>(*it);
|
||||
ListenerManager::iterator it = m_LstList.begin();
|
||||
while (it != m_LstList.end()) { // check for active events
|
||||
JumpListener* pje = dynamic_cast<JumpListener*>(*it);
|
||||
if (pje != NULL) {
|
||||
pje->setOpcode(opcode);
|
||||
pje->setFlagTriggered(flagTriggered);
|
||||
it = m_EvList.makeActive(it);
|
||||
it = m_LstList.makeActive(it);
|
||||
continue; // dito.
|
||||
}
|
||||
++it;
|
||||
}
|
||||
m_EvList.fireActiveEvents();
|
||||
m_LstList.triggerActiveListeners();
|
||||
}
|
||||
|
||||
void SimulatorController::addFlow(ExperimentFlow* flow)
|
||||
@ -215,15 +215,15 @@ void SimulatorController::addFlow(ExperimentFlow* flow)
|
||||
void SimulatorController::removeFlow(ExperimentFlow* flow)
|
||||
{
|
||||
// remove all remaining events of this flow
|
||||
clearEvents(flow);
|
||||
clearListeners(flow);
|
||||
// remove coroutine
|
||||
m_Flows.remove(flow);
|
||||
}
|
||||
|
||||
BaseEvent* SimulatorController::addEventAndWait(BaseEvent* ev)
|
||||
BaseListener* SimulatorController::addListenerAndResume(BaseListener* li)
|
||||
{
|
||||
addEvent(ev);
|
||||
return waitAny();
|
||||
addListener(li);
|
||||
return resume();
|
||||
}
|
||||
|
||||
void SimulatorController::terminate(int exCode)
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
#include <vector>
|
||||
|
||||
#include "efw/CoroutineManager.hpp"
|
||||
#include "EventManager.hpp"
|
||||
#include "ListenerManager.hpp"
|
||||
#include "SALConfig.hpp"
|
||||
#include "Event.hpp"
|
||||
#include "Listener.hpp"
|
||||
|
||||
namespace fail {
|
||||
|
||||
@ -23,23 +23,22 @@ class MemoryManager;
|
||||
*
|
||||
* \brief The abstract interface for controlling simulators and
|
||||
* accessing experiment data/flows (as part of the "Simulator
|
||||
* Abstraction Layer".
|
||||
* Abstraction Layer", SAL for short).
|
||||
*
|
||||
* This class manages (1..N) experiments and provides access to the underlying
|
||||
* simulator/debugger system. Experiments can enlist arbritrary events
|
||||
* (Breakpoint, Memory access, Traps, etc.). The SimulatorController then
|
||||
* simulator/debugger system. Experiments can enlist arbritrary listeners
|
||||
* (Breakpoint, Memory access, Traps, etc.). The \c SimulatorController then
|
||||
* activates the specific experiment There are further methods to read/write
|
||||
* registers and memory, and control the SUT (save/restore/reset).
|
||||
*/
|
||||
class SimulatorController {
|
||||
protected:
|
||||
EventManager m_EvList; //!< storage where events are being buffered
|
||||
ListenerManager m_LstList; //!< storage where listeners are being buffered
|
||||
CoroutineManager m_Flows; //!< managed experiment flows
|
||||
RegisterManager *m_Regs; //!< access to cpu register
|
||||
MemoryManager *m_Mem; //!< access to memory pool
|
||||
//! list of suppressed interrupts
|
||||
std::vector<unsigned> m_SuppressedInterrupts;
|
||||
friend class EventManager; //!< "outsources" the event management
|
||||
std::vector<unsigned> m_SuppressedInterrupts; //!< list of suppressed interrupts
|
||||
friend class ListenerManager; //!< "outsources" the listener management
|
||||
public:
|
||||
SimulatorController()
|
||||
: m_Regs(NULL), m_Mem(NULL) { }
|
||||
@ -60,17 +59,17 @@ public:
|
||||
*/
|
||||
void initExperiments();
|
||||
/* ********************************************************************
|
||||
* Standard Event Handler API
|
||||
* Standard Listener Handler API
|
||||
* ********************************************************************/
|
||||
/**
|
||||
* Breakpoint event handler. This routine needs to be called in the
|
||||
* simulator specific backend each time a breakpoint event occurs.
|
||||
* @param instrPtr the instruction pointer of the breakpoint event
|
||||
* Breakpoint listener handler. This routine needs to be called in the
|
||||
* simulator specific backend each time a breakpoint listener occurs.
|
||||
* @param instrPtr the instruction pointer of the breakpoint listener
|
||||
* @param address_space the address space it should occur in
|
||||
*/
|
||||
void onBreakpointEvent(address_t instrPtr, address_t address_space);
|
||||
void onBreakpointListener(address_t instrPtr, address_t address_space);
|
||||
/**
|
||||
* Memory access event handler (read/write).
|
||||
* Memory access listener handler (read/write).
|
||||
* @param addr the accessed memory address
|
||||
* @param len the length of the accessed memory
|
||||
* @param is_write \c true if memory is written, \c false if read
|
||||
@ -79,32 +78,31 @@ public:
|
||||
*
|
||||
* FIXME: should instrPtr be part of this interface?
|
||||
*/
|
||||
void onMemoryAccessEvent(address_t addr, size_t len,
|
||||
bool is_write, address_t instrPtr);
|
||||
void onMemoryAccessListener(address_t addr, size_t len, bool is_write, address_t instrPtr);
|
||||
/**
|
||||
* Interrupt event handler.
|
||||
* Interrupt listener handler.
|
||||
* @param interruptNum the interrupt-type id
|
||||
* @param nmi nmi-value from guest-system
|
||||
*/
|
||||
void onInterruptEvent(unsigned interruptNum, bool nmi);
|
||||
void onInterruptListener(unsigned interruptNum, bool nmi);
|
||||
/**
|
||||
* Trap event handler.
|
||||
* Trap listener handler.
|
||||
* @param trapNum the trap-type id
|
||||
*/
|
||||
void onTrapEvent(unsigned trapNum);
|
||||
void onTrapListener(unsigned trapNum);
|
||||
/**
|
||||
* Guest system communication handler.
|
||||
* @param data the "message" from the guest system
|
||||
* @param port the port of the event
|
||||
* @param port the port of the listener
|
||||
*/
|
||||
void onGuestSystemEvent(char data, unsigned port);
|
||||
void onGuestSystemListener(char data, unsigned port);
|
||||
/**
|
||||
* (Conditional) Jump-instruction handler.
|
||||
* @param flagTriggered \c true if the jump was triggered due to a
|
||||
* specific FLAG (zero/carry/sign/overflow/parity flag)
|
||||
* @param opcode the opcode of the conrecete jump instruction
|
||||
*/
|
||||
void onJumpEvent(bool flagTriggered, unsigned opcode);
|
||||
void onJumpListener(bool flagTriggered, unsigned opcode);
|
||||
/* ********************************************************************
|
||||
* Simulator Controller & Access API:
|
||||
* ********************************************************************/
|
||||
@ -115,7 +113,7 @@ public:
|
||||
virtual void save(const std::string& path) = 0;
|
||||
/**
|
||||
* Restore simulator state. Implicitly discards all previously
|
||||
* registered events.
|
||||
* registered listeners.
|
||||
* @param path Location to previously saved state information
|
||||
*/
|
||||
virtual void restore(const std::string& path) = 0;
|
||||
@ -144,20 +142,18 @@ public:
|
||||
/**
|
||||
* Remove a Interrupt from the list of suppressed.
|
||||
* @param interruptNum the interrupt-type id
|
||||
* @return \c true if sucessfully removed, \c false otherwise (not
|
||||
* found)
|
||||
* @return \c true if sucessfully removed, \c false otherwise (not found)
|
||||
*/
|
||||
bool removeSuppressedInterrupt(unsigned interruptNum);
|
||||
/**
|
||||
* Returns the (constant) initialized register manager.
|
||||
* @return a reference to the register manager
|
||||
*/
|
||||
RegisterManager& getRegisterManager() { return (*m_Regs); }
|
||||
const RegisterManager& getRegisterManager() const { return (*m_Regs); }
|
||||
RegisterManager& getRegisterManager() { return *m_Regs; }
|
||||
const RegisterManager& getRegisterManager() const { return *m_Regs; }
|
||||
/**
|
||||
* Sets the register manager.
|
||||
* @param pReg the new register manager (or a concrete derived class of
|
||||
* RegisterManager)
|
||||
* @param pReg the new register manager (or a concrete derived class of \c RegisterManager)
|
||||
*/
|
||||
void setRegisterManager(RegisterManager* pReg) { m_Regs = pReg; }
|
||||
/**
|
||||
@ -165,74 +161,72 @@ public:
|
||||
* @return a reference to the memory manager
|
||||
*/
|
||||
MemoryManager& getMemoryManager() { return (*m_Mem); }
|
||||
const MemoryManager& getMemoryManager() const { return (*m_Mem); }
|
||||
const MemoryManager& getMemoryManager() const { return *m_Mem; }
|
||||
/**
|
||||
* Sets the memory manager.
|
||||
* @param pMem a new concrete memory manager
|
||||
*/
|
||||
void setMemoryManager(MemoryManager* pMem) { m_Mem = pMem; }
|
||||
/* ********************************************************************
|
||||
* Experiment-Flow & Event Management API:
|
||||
* Experiment-Flow & Listener Management API:
|
||||
* ********************************************************************/
|
||||
/**
|
||||
* Adds the specified experiment or plugin and creates a coroutine to
|
||||
* run it in.
|
||||
* Adds the specified experiment or plugin and creates a coroutine to run it in.
|
||||
* @param flow the experiment flow object to be added
|
||||
*/
|
||||
void addFlow(ExperimentFlow* flow);
|
||||
/**
|
||||
* Removes the specified experiment or plugin and destroys its coroutine
|
||||
* and all associated events.
|
||||
* and all associated listeners.
|
||||
* @param flow the experiment flow object to be removed
|
||||
*/
|
||||
void removeFlow(ExperimentFlow* flow);
|
||||
/**
|
||||
* Add event ev to the event management. This causes the event to be
|
||||
* active.
|
||||
* @param ev the event pointer to be added for the current flow
|
||||
* @return \c true if the event has been added successfully, \c false otherwise
|
||||
* Add listener \c li to the listener management. This causes the listener to be active.
|
||||
* @param li the listener pointer to be added for the current flow
|
||||
* @return \c true if the listener has been added successfully, \c false otherwise
|
||||
*/
|
||||
bool addEvent(BaseEvent* ev);
|
||||
bool addListener(BaseListener* li);
|
||||
/**
|
||||
* Removes the event with the specified id.
|
||||
* @param ev the pointer of the event-object to be removed; if \a ev is
|
||||
* equal to \c NULL all events (for all experiments) will be
|
||||
* removed
|
||||
* Removes the listener with the specified pointer \c li.
|
||||
* @param li the pointer of the listener-object to be removed; if \c li is
|
||||
* equal to \c NULL, all listeners (for all experiments) will be removed
|
||||
*/
|
||||
void removeEvent(BaseEvent* ev) { m_EvList.remove(ev); }
|
||||
void removeListener(BaseListener* li) { m_LstList.remove(li); }
|
||||
/**
|
||||
* Removes all previously added events for all experiments. To
|
||||
* Removes all previously added listeners for all experiments. To
|
||||
* restrict this to a specific experiment flow, pass a pointer to it.
|
||||
* @param flow a ptr to a specific experiment flow whose listeners should
|
||||
* be removed; if \c flow is \c NULL, all listeners will be removed
|
||||
*/
|
||||
void clearEvents(ExperimentFlow *flow = 0) { m_EvList.remove(flow); }
|
||||
void clearListeners(ExperimentFlow *flow = 0) { m_LstList.remove(flow); }
|
||||
/**
|
||||
* Waits on any events which have been added to the event management. If
|
||||
* one of those events occour, waitAny() will return the id of that event.
|
||||
* @return the previously occurred event, or \c NULL if there are no
|
||||
* events to wait for
|
||||
* Switches the control flow to the simulator and waits on any listeners
|
||||
* which have been added to the listener management. If one of those listeners
|
||||
* occurs, resume() will return the pointer of that listener.
|
||||
* @return the previously occurred listener, or \c NULL if there are no
|
||||
* listeners to wait for
|
||||
*/
|
||||
BaseEvent* waitAny();
|
||||
BaseListener* resume();
|
||||
/**
|
||||
* Add event \a ev to the global buffer and wait for it (combines
|
||||
* \c addEvent() and \c waitAny()).
|
||||
* @param ev the event pointer to be added
|
||||
* @return the pointer of the occurred event (it is not guaranteed that
|
||||
* this pointer will be equal to ev)
|
||||
*
|
||||
* FIXME: Rename to make clear this returns when *any* event occurs
|
||||
* Add listener \a ev to the global buffer and continues the simulation
|
||||
* (combines \c addListener() and \c resume()).
|
||||
* @param li the listener pointer to be added
|
||||
* @return the pointer of the occurred listener (it is not guaranteed that
|
||||
* this pointer will be equal to li)
|
||||
*/
|
||||
BaseEvent* addEventAndWait(BaseEvent* ev);
|
||||
BaseListener* addListenerAndResume(BaseListener* li);
|
||||
/**
|
||||
* Checks whether any experiment flow has events in the event-list.
|
||||
* @return \c true if there are still events, or \c false otherwise
|
||||
* Checks whether any experiment flow has listeners in the listener-list.
|
||||
* @return \c true if there are still listeners, or \c false otherwise
|
||||
*/
|
||||
bool hasEvents() const { return getEventCount() > 0; }
|
||||
bool hasListeners() const { return getListenerCount() > 0; }
|
||||
/**
|
||||
* Determines the number of (stored) events in the event-list which have
|
||||
* Determines the number of (stored) listeners in the listener-list which have
|
||||
* not been triggered so far.
|
||||
* @return the actual number of events
|
||||
* @return the actual number of listeners
|
||||
*/
|
||||
unsigned getEventCount() const { return m_EvList.getEventCount(); }
|
||||
unsigned getListenerCount() const { return m_LstList.getListenerCount(); }
|
||||
};
|
||||
|
||||
} // end-of-namespace: fail
|
||||
|
||||
@ -101,13 +101,13 @@ void BochsController::onInstrPtrChanged(address_t instrPtr, address_t address_sp
|
||||
m_CacheEntry = cache_entry;
|
||||
bool do_fire = false;
|
||||
// Check for active breakpoint-events:
|
||||
bp_cache_t &buffer_cache = m_EvList.getBPBuffer();
|
||||
bp_cache_t &buffer_cache = m_LstList.getBPBuffer();
|
||||
bp_cache_t::iterator it = buffer_cache.begin();
|
||||
while (it != buffer_cache.end()) {
|
||||
BPEvent* pEvBreakpt = *it;
|
||||
BPListener* pEvBreakpt = *it;
|
||||
if (pEvBreakpt->isMatching(instrPtr, address_space)) {
|
||||
pEvBreakpt->setTriggerInstructionPointer(instrPtr);
|
||||
it = buffer_cache.makeActive(m_EvList, it);
|
||||
it = buffer_cache.makeActive(m_LstList, it);
|
||||
do_fire = true;
|
||||
// "it" has already been set to the next element (by calling
|
||||
// makeActive()):
|
||||
@ -116,28 +116,28 @@ void BochsController::onInstrPtrChanged(address_t instrPtr, address_t address_sp
|
||||
it++;
|
||||
}
|
||||
if (do_fire)
|
||||
m_EvList.fireActiveEvents();
|
||||
// Note: SimulatorController::onBreakpointEvent will not be invoked in this
|
||||
m_LstList.triggerActiveListeners();
|
||||
// Note: SimulatorController::onBreakpointListener will not be invoked in this
|
||||
// implementation.
|
||||
}
|
||||
|
||||
void BochsController::onIOPortEvent(unsigned char data, unsigned port, bool out) {
|
||||
void BochsController::onIOPortListener(unsigned char data, unsigned port, bool out) {
|
||||
// Check for active breakpoint-events:
|
||||
io_cache_t &buffer_cache = m_EvList.getIOBuffer();
|
||||
io_cache_t &buffer_cache = m_LstList.getIOBuffer();
|
||||
io_cache_t::iterator it = buffer_cache.begin();
|
||||
while (it != buffer_cache.end()) {
|
||||
IOPortEvent* pIOPt = (*it);
|
||||
IOPortListener* pIOPt = (*it);
|
||||
if (pIOPt->isMatching(port, out)) {
|
||||
pIOPt->setData(data);
|
||||
it = buffer_cache.makeActive(m_EvList, it);
|
||||
it = buffer_cache.makeActive(m_LstList, it);
|
||||
// "it" has already been set to the next element (by calling
|
||||
// makeActive()):
|
||||
continue; // -> skip iterator increment
|
||||
}
|
||||
it++;
|
||||
}
|
||||
m_EvList.fireActiveEvents();
|
||||
// Note: SimulatorController::onBreakpointEvent will not be invoked in this
|
||||
m_LstList.triggerActiveListeners();
|
||||
// Note: SimulatorController::onBreakpointListener will not be invoked in this
|
||||
// implementation.
|
||||
}
|
||||
|
||||
@ -165,7 +165,7 @@ void BochsController::saveDone()
|
||||
|
||||
void BochsController::restore(const std::string& path)
|
||||
{
|
||||
clearEvents();
|
||||
clearListeners();
|
||||
restore_bochs_request = true;
|
||||
BX_CPU(0)->async_event |= 1;
|
||||
sr_path = path;
|
||||
@ -181,7 +181,7 @@ void BochsController::restoreDone()
|
||||
|
||||
void BochsController::reboot()
|
||||
{
|
||||
clearEvents();
|
||||
clearListeners();
|
||||
reboot_bochs_request = true;
|
||||
BX_CPU(0)->async_event |= 1;
|
||||
m_CurrFlow = m_Flows.getCurrent();
|
||||
@ -213,16 +213,16 @@ void BochsController::onTimerTrigger(void* thisPtr)
|
||||
{
|
||||
// FIXME: The timer logic can be modified to use only one timer in Bochs.
|
||||
// (For now, this suffices.)
|
||||
TimerEvent* pTmEv = static_cast<TimerEvent*>(thisPtr);
|
||||
// Check for a matching TimerEvent. (In fact, we are only
|
||||
// interessted in the iterator pointing at pTmEv.):
|
||||
EventManager::iterator it = std::find(simulator.m_EvList.begin(),
|
||||
simulator.m_EvList.end(), pTmEv);
|
||||
// TODO: This has O(|m_EvList|) time complexity. We can further improve this
|
||||
// by creating a method such that makeActive(pTmEv) works as well,
|
||||
TimerListener* pli = static_cast<TimerListener*>(thisPtr);
|
||||
// Check for a matching TimerListener. (In fact, we are only
|
||||
// interessted in the iterator pointing at pli.)
|
||||
ListenerManager::iterator it = std::find(simulator.m_LstList.begin(),
|
||||
simulator.m_LstList.end(), pli);
|
||||
// TODO: This has O(|m_LstList|) time complexity. We can further improve this
|
||||
// by creating a method such that makeActive(pli) works as well,
|
||||
// reducing the time complexity to O(1).
|
||||
simulator.m_EvList.makeActive(it);
|
||||
simulator.m_EvList.fireActiveEvents();
|
||||
simulator.m_LstList.makeActive(it);
|
||||
simulator.m_LstList.triggerActiveListeners();
|
||||
}
|
||||
|
||||
const std::string& BochsController::getMnemonic() const
|
||||
|
||||
@ -8,10 +8,10 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "FailBochsGlobals.hpp"
|
||||
#include "BochsEvents.hpp"
|
||||
#include "BochsListener.hpp"
|
||||
|
||||
#include "../SimulatorController.hpp"
|
||||
#include "../Event.hpp"
|
||||
#include "../Listener.hpp"
|
||||
|
||||
#include "bochs.h"
|
||||
#include "cpu/cpu.h"
|
||||
@ -40,7 +40,7 @@ public:
|
||||
BochsController();
|
||||
~BochsController();
|
||||
/* ********************************************************************
|
||||
* Standard Event Handler API:
|
||||
* Standard Listener Handler API:
|
||||
* ********************************************************************/
|
||||
/**
|
||||
* Instruction pointer modification handler. This method is called (from
|
||||
@ -57,20 +57,20 @@ public:
|
||||
* @param port the port it was transmitted on
|
||||
* @param out true if the I/O traffic has been outbound, false otherwise
|
||||
*/
|
||||
void onIOPortEvent(unsigned char data, unsigned port, bool out);
|
||||
void onIOPortListener(unsigned char data, unsigned port, bool out);
|
||||
/**
|
||||
* Static internal event handler for TimerEvents. This static function is
|
||||
* Static internal handler for TimerListeners. This static function is
|
||||
* called when a previously registered (Bochs) timer triggers. This function
|
||||
* searches for the provided TimerEvent object within the EventManager and
|
||||
* fires such an event by calling \c fireActiveEvents().
|
||||
* @param thisPtr a pointer to the TimerEvent-object triggered
|
||||
* searches for the provided TimerListener object within the ListenerManager and
|
||||
* fires such an event by calling \c triggerActiveListeners().
|
||||
* @param thisPtr a pointer to the TimerListener-object triggered
|
||||
*
|
||||
* FIXME: Due to Bochs internal timer and ips-configuration related stuff,
|
||||
* the simulator sometimes panics with "keyboard error:21" (see line
|
||||
* 1777 in bios/rombios.c, function keyboard_init()) if a TimerEvent
|
||||
* 1777 in bios/rombios.c, function keyboard_init()) if a TimerListener
|
||||
* is added *before* the bios has been loaded and initialized. To
|
||||
* reproduce this error, try adding a \c TimerEvent as the initial step
|
||||
* in your experiment code and wait for it (\c addEventAndWait()).
|
||||
* reproduce this error, try adding a \c TimerListener as the initial step
|
||||
* in your experiment code and wait for it (\c addListenerAndResume()).
|
||||
*/
|
||||
static void onTimerTrigger(void *thisPtr);
|
||||
/* ********************************************************************
|
||||
@ -86,7 +86,7 @@ public:
|
||||
*/
|
||||
void saveDone();
|
||||
/**
|
||||
* Restore simulator state. Clears all Events.
|
||||
* Restore simulator state. Clears all Listeners.
|
||||
* @param path Location to previously saved state information
|
||||
*/
|
||||
void restore(const std::string& path);
|
||||
@ -95,7 +95,7 @@ public:
|
||||
*/
|
||||
void restoreDone();
|
||||
/**
|
||||
* Reboot simulator. Clears all Events.
|
||||
* Reboot simulator. Clears all Listeners.
|
||||
*/
|
||||
void reboot();
|
||||
/**
|
||||
|
||||
@ -1,34 +1,34 @@
|
||||
#include "BochsEvents.hpp"
|
||||
#include "BochsListener.hpp"
|
||||
#include "../SALInst.hpp"
|
||||
|
||||
namespace fail {
|
||||
|
||||
bool TimerEvent::onEventAddition()
|
||||
bool TimerListener::onAddition()
|
||||
{
|
||||
// Register the timer event in the Bochs simulator:
|
||||
// Register the timer listener in the Bochs simulator:
|
||||
setId(m_registerTimer(this));
|
||||
if(getId() == -1)
|
||||
return false; // unable to register the timer (error in Bochs' function call)
|
||||
return true;
|
||||
}
|
||||
|
||||
void TimerEvent::onEventDeletion()
|
||||
void TimerListener::onDeletion()
|
||||
{
|
||||
// Unregister the time event:
|
||||
// Unregister the time listener:
|
||||
m_unregisterTimer(this);
|
||||
}
|
||||
|
||||
timer_id_t TimerEvent::m_registerTimer(TimerEvent* pev)
|
||||
timer_id_t TimerListener::m_registerTimer(TimerListener* pev)
|
||||
{
|
||||
assert(pev != NULL && "FATAL ERROR: TimerEvent object ptr cannot be NULL!");
|
||||
assert(pev != NULL && "FATAL ERROR: TimerListener object ptr cannot be NULL!");
|
||||
return static_cast<timer_id_t>(
|
||||
bx_pc_system.register_timer(pev, BochsController::onTimerTrigger, pev->getTimeout(),
|
||||
false, 1/*start immediately*/, "Fail*: BochsController"/*name*/));
|
||||
}
|
||||
|
||||
bool TimerEvent::m_unregisterTimer(TimerEvent* pev)
|
||||
bool TimerListener::m_unregisterTimer(TimerListener* pev)
|
||||
{
|
||||
assert(pev != NULL && "FATAL ERROR: TimerEvent object ptr cannot be NULL!");
|
||||
assert(pev != NULL && "FATAL ERROR: TimerListener object ptr cannot be NULL!");
|
||||
bx_pc_system.deactivate_timer(static_cast<unsigned>(pev->getId()));
|
||||
return bx_pc_system.unregisterTimer(static_cast<unsigned>(pev->getId()));
|
||||
}
|
||||
@ -1,74 +1,76 @@
|
||||
#ifndef __BOCHS_EVENTS_HPP__
|
||||
#define __BOCHS_EVENTS_HPP__
|
||||
|
||||
#include "../Event.hpp"
|
||||
#include "../Listener.hpp"
|
||||
|
||||
#include "BochsController.hpp"
|
||||
|
||||
namespace fail {
|
||||
|
||||
/**
|
||||
* \class TimerEvent
|
||||
* Concrete TimerEvent implementation of GenericTimerEvent for the Bochs
|
||||
* \class TimerListener
|
||||
* Concrete TimerListener implementation of GenericTimerListener for the Bochs
|
||||
* simulator backend.
|
||||
*/
|
||||
class TimerEvent : public GenericTimerEvent {
|
||||
class TimerListener : public GenericTimerListener {
|
||||
private:
|
||||
/**
|
||||
* Registers a timer in the Bochs simulator. This timer fires \a TimerEvents
|
||||
* Registers a timer in the Bochs simulator. This timer triggers \a TimerListeners
|
||||
* to inform the corresponding experiment-flow. Note that the number of timers
|
||||
* (in Bochs) is limited to \c BX_MAX_TIMERS (defaults to 64 in v2.4.6).
|
||||
* @param pev a pointer to the (experiment flow-) allocated TimerEvent object,
|
||||
* providing all required information to start the time, e.g. the
|
||||
* timeout value.
|
||||
* @return \c The unique id of the timer recently created. This id is carried
|
||||
* along with the TimerEvent, @see getId(). On errors, -1 is returned
|
||||
* (e.g. because a timer with the same id is already existing)
|
||||
* @param pli a pointer to the (experiment flow-) allocated TimerListener object,
|
||||
* providing all required information to start the timer (e.g. the
|
||||
* timeout value).
|
||||
* @return The unique id of the timer recently created. This id is carried
|
||||
* along with the TimerListener. On errors, -1 is returned (e.g. because
|
||||
* a timer with the same id is already existing)
|
||||
* @see TimerListener::getId()
|
||||
*/
|
||||
static timer_id_t m_registerTimer(TimerEvent* pev);
|
||||
static timer_id_t m_registerTimer(TimerListener* pli);
|
||||
/**
|
||||
* Deletes a timer. No further events will be triggered by the timer.
|
||||
* @param pev a pointer to the TimerEvent-object to be removed
|
||||
* @return \c true if the timer with \a pev->getId() has been removed
|
||||
* @param pli a pointer to the TimerListener-object to be removed
|
||||
* @return \c true if the timer with \a pli->getId() has been removed
|
||||
* successfully, \c false otherwise
|
||||
*/
|
||||
static bool m_unregisterTimer(TimerEvent* pev);
|
||||
static bool m_unregisterTimer(TimerListener* pli);
|
||||
public:
|
||||
/**
|
||||
* Creates a new timer event. This can be used to implement a timeout-
|
||||
* mechanism in the experiment-flow. The timer starts automatically when
|
||||
* added to FailBochs.
|
||||
* @param timeout the time intervall in milliseconds (ms)
|
||||
* @see SimulatorController::addEvent
|
||||
* @see SimulatorController::addListener
|
||||
*/
|
||||
TimerEvent(unsigned timeout)
|
||||
: GenericTimerEvent(timeout) { }
|
||||
~TimerEvent() { onEventDeletion(); }
|
||||
TimerListener(unsigned timeout)
|
||||
: GenericTimerListener(timeout) { }
|
||||
~TimerListener() { onDeletion(); }
|
||||
|
||||
/**
|
||||
* This method is called when an experiment flow adds a new event by
|
||||
* calling \c simulator.addEvent(pev) or \c simulator.addEventAndWait(pev).
|
||||
* calling \c simulator.addListener() or \c simulator.addListenerAndResume().
|
||||
* More specifically, the event will be added to the event-list first
|
||||
* (buffer-list, to be precise) and then this event handler is called.
|
||||
* @return You should return \c true to continue and \c false to prevent
|
||||
* the addition of the event \a pev, yielding an error in the
|
||||
* experiment flow (i.e. -1 is returned).
|
||||
*/
|
||||
bool onEventAddition();
|
||||
bool onAddition();
|
||||
/**
|
||||
* This method is called when an experiment flow removes an event from
|
||||
* the event-management by calling \c removeEvent(prev), \c clearEvents()
|
||||
* the event-management by calling \c removeListener(prev), \c clearListeners()
|
||||
* or by deleting a complete flow (\c removeFlow). More specifically, this
|
||||
* event handler will be called *before* the event is actually deleted.
|
||||
*/
|
||||
void onEventDeletion();
|
||||
void onDeletion();
|
||||
/**
|
||||
* This method is called when an previously added event is about to be
|
||||
* triggered by the simulator-backend. More specifically, this event handler
|
||||
* will be called *before* the event is actually triggered, i.e. before the
|
||||
* corresponding coroutine is toggled.
|
||||
*/
|
||||
void onEventTrigger() { onEventDeletion(); }
|
||||
void onTrigger() { onDeletion(); }
|
||||
// TODO/FIXME: bei neuer impl. anpassen
|
||||
};
|
||||
|
||||
} // end-of-namespace: fail
|
||||
@ -32,7 +32,7 @@ public:
|
||||
* Retrieves the data of the register.
|
||||
* @return the current register data
|
||||
*/
|
||||
regdata_t getData() { return (*m_pData); }
|
||||
regdata_t getData() { return *m_pData; }
|
||||
/**
|
||||
* Sets the content of the register.
|
||||
* @param data the new register data to be written
|
||||
@ -136,44 +136,42 @@ public:
|
||||
* Returns \c true if the corresponding flag is set, or \c false
|
||||
* otherwise.
|
||||
*/
|
||||
bool getCarryFlag() const { return (BX_CPU(0)->get_CF()); }
|
||||
bool getParityFlag() const { return (BX_CPU(0)->get_PF()); }
|
||||
bool getZeroFlag() const { return (BX_CPU(0)->get_ZF()); }
|
||||
bool getSignFlag() const { return (BX_CPU(0)->get_SF()); }
|
||||
bool getOverflowFlag() const { return (BX_CPU(0)->get_OF()); }
|
||||
|
||||
bool getTrapFlag() const { return (BX_CPU(0)->get_TF()); }
|
||||
bool getInterruptFlag() const { return (BX_CPU(0)->get_IF()); }
|
||||
bool getDirectionFlag() const { return (BX_CPU(0)->get_DF()); }
|
||||
unsigned getIOPrivilegeLevel() const { return (BX_CPU(0)->get_IOPL()); }
|
||||
bool getNestedTaskFlag() const { return (BX_CPU(0)->get_NT()); }
|
||||
bool getResumeFlag() const { return (BX_CPU(0)->get_RF()); }
|
||||
bool getVMFlag() const { return (BX_CPU(0)->get_VM()); }
|
||||
bool getAlignmentCheckFlag() const { return (BX_CPU(0)->get_AC()); }
|
||||
bool getVInterruptFlag() const { return (BX_CPU(0)->get_VIF()); }
|
||||
bool getVInterruptPendingFlag() const { return (BX_CPU(0)->get_VIP()); }
|
||||
bool getIdentificationFlag() const { return (BX_CPU(0)->get_ID()); }
|
||||
bool getCarryFlag() const { return BX_CPU(0)->get_CF(); }
|
||||
bool getParityFlag() const { return BX_CPU(0)->get_PF(); }
|
||||
bool getZeroFlag() const { return BX_CPU(0)->get_ZF(); }
|
||||
bool getSignFlag() const { return BX_CPU(0)->get_SF(); }
|
||||
bool getOverflowFlag() const { return BX_CPU(0)->get_OF(); }
|
||||
bool getTrapFlag() const { return BX_CPU(0)->get_TF(); }
|
||||
bool getInterruptFlag() const { return BX_CPU(0)->get_IF(); }
|
||||
bool getDirectionFlag() const { return BX_CPU(0)->get_DF(); }
|
||||
unsigned getIOPrivilegeLevel() const { return BX_CPU(0)->get_IOPL(); }
|
||||
bool getNestedTaskFlag() const { return BX_CPU(0)->get_NT(); }
|
||||
bool getResumeFlag() const { return BX_CPU(0)->get_RF(); }
|
||||
bool getVMFlag() const { return BX_CPU(0)->get_VM(); }
|
||||
bool getAlignmentCheckFlag() const { return BX_CPU(0)->get_AC(); }
|
||||
bool getVInterruptFlag() const { return BX_CPU(0)->get_VIF(); }
|
||||
bool getVInterruptPendingFlag() const { return BX_CPU(0)->get_VIP(); }
|
||||
bool getIdentificationFlag() const { return BX_CPU(0)->get_ID(); }
|
||||
|
||||
/**
|
||||
* Sets/resets various status FLAGS.
|
||||
*/
|
||||
void setCarryFlag(bool bit) { BX_CPU(0)->set_CF(bit); }
|
||||
void setParityFlag(bool bit) { BX_CPU(0)->set_PF(bit); }
|
||||
void setZeroFlag(bool bit) { BX_CPU(0)->set_ZF(bit); }
|
||||
void setSignFlag(bool bit) { BX_CPU(0)->set_SF(bit); }
|
||||
void setOverflowFlag(bool bit) { BX_CPU(0)->set_OF(bit); }
|
||||
|
||||
void setTrapFlag(bool bit) { BX_CPU(0)->set_TF(bit); }
|
||||
void setInterruptFlag(bool bit) { BX_CPU(0)->set_IF(bit); }
|
||||
void setDirectionFlag(bool bit) { BX_CPU(0)->set_DF(bit); }
|
||||
void setIOPrivilegeLevel(unsigned lvl) { BX_CPU(0)->set_IOPL(lvl); }
|
||||
void setNestedTaskFlag(bool bit) { BX_CPU(0)->set_NT(bit); }
|
||||
void setResumeFlag(bool bit) { BX_CPU(0)->set_RF(bit); }
|
||||
void setVMFlag(bool bit) { BX_CPU(0)->set_VM(bit); }
|
||||
void setAlignmentCheckFlag(bool bit) { BX_CPU(0)->set_AC(bit); }
|
||||
void setVInterruptFlag(bool bit) { BX_CPU(0)->set_VIF(bit); }
|
||||
void setVInterruptPendingFlag(bool bit) { BX_CPU(0)->set_VIP(bit); }
|
||||
void setIdentificationFlag(bool bit) { BX_CPU(0)->set_ID(bit); }
|
||||
void setCarryFlag(bool bit) { BX_CPU(0)->set_CF(bit); }
|
||||
void setParityFlag(bool bit) { BX_CPU(0)->set_PF(bit); }
|
||||
void setZeroFlag(bool bit) { BX_CPU(0)->set_ZF(bit); }
|
||||
void setSignFlag(bool bit) { BX_CPU(0)->set_SF(bit); }
|
||||
void setOverflowFlag(bool bit) { BX_CPU(0)->set_OF(bit); }
|
||||
void setTrapFlag(bool bit) { BX_CPU(0)->set_TF(bit); }
|
||||
void setInterruptFlag(bool bit) { BX_CPU(0)->set_IF(bit); }
|
||||
void setDirectionFlag(bool bit) { BX_CPU(0)->set_DF(bit); }
|
||||
void setIOPrivilegeLevel(unsigned lvl) { BX_CPU(0)->set_IOPL(lvl); }
|
||||
void setNestedTaskFlag(bool bit) { BX_CPU(0)->set_NT(bit); }
|
||||
void setResumeFlag(bool bit) { BX_CPU(0)->set_RF(bit); }
|
||||
void setVMFlag(bool bit) { BX_CPU(0)->set_VM(bit); }
|
||||
void setAlignmentCheckFlag(bool bit) { BX_CPU(0)->set_AC(bit); }
|
||||
void setVInterruptFlag(bool bit) { BX_CPU(0)->set_VIF(bit); }
|
||||
void setVInterruptPendingFlag(bool bit) { BX_CPU(0)->set_VIP(bit); }
|
||||
void setIdentificationFlag(bool bit) { BX_CPU(0)->set_ID(bit); }
|
||||
|
||||
/**
|
||||
* Sets the content of the status register.
|
||||
@ -184,8 +182,7 @@ public:
|
||||
{
|
||||
#ifdef BX_SUPPORT_X86_64
|
||||
// We are in 64 bit mode: Just assign the lower 32 bits!
|
||||
(*m_pData) = ((*m_pData) & 0xFFFFFFFF00000000ULL) |
|
||||
(data & 0xFFFFFFFFULL);
|
||||
(*m_pData) = ((*m_pData) & 0xFFFFFFFF00000000ULL) | (data & 0xFFFFFFFFULL);
|
||||
#else
|
||||
*m_pData = data;
|
||||
#endif
|
||||
@ -204,7 +201,7 @@ public:
|
||||
*/
|
||||
address_t getInstructionPointer()
|
||||
{
|
||||
return (static_cast<address_t>(getSetOfType(RT_PC)->first()->getData()));
|
||||
return static_cast<address_t>(getSetOfType(RT_PC)->first()->getData());
|
||||
}
|
||||
/**
|
||||
* Retruns the top address of the stack.
|
||||
@ -213,9 +210,9 @@ public:
|
||||
address_t getStackPointer()
|
||||
{
|
||||
#if BX_SUPPORT_X86_64
|
||||
return (static_cast<address_t>(getRegister(RID_RSP)->getData()));
|
||||
return static_cast<address_t>(getRegister(RID_RSP)->getData());
|
||||
#else
|
||||
return (static_cast<address_t>(getRegister(RID_ESP)->getData()));
|
||||
return static_cast<address_t>(getRegister(RID_ESP)->getData());
|
||||
#endif
|
||||
}
|
||||
/**
|
||||
@ -226,9 +223,9 @@ public:
|
||||
address_t getBasePointer()
|
||||
{
|
||||
#if BX_SUPPORT_X86_64
|
||||
return (static_cast<address_t>(getRegister(RID_RBP)->getData()));
|
||||
return static_cast<address_t>(getRegister(RID_RBP)->getData());
|
||||
#else
|
||||
return (static_cast<address_t>(getRegister(RID_EBP)->getData()));
|
||||
return static_cast<address_t>(getRegister(RID_EBP)->getData());
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
aspect Breakpoints {
|
||||
pointcut cpuLoop() = "void defineCPULoopJoinPoint(...)";
|
||||
|
||||
advice execution (cpuLoop()) : after () // Event source: "instruction pointer"
|
||||
advice execution (cpuLoop()) : after () // event source: "instruction pointer"
|
||||
{
|
||||
// Points to the cpu class: "this" if BX_USE_CPU_SMF == 0,
|
||||
// BX_CPU(0) otherwise
|
||||
|
||||
@ -19,12 +19,12 @@
|
||||
aspect GuestSysCom {
|
||||
pointcut outInstructions() = "% ...::bx_cpu_c::OUT_DX%(...)";
|
||||
|
||||
advice execution (outInstructions()) : after () // Event source: "guest system"
|
||||
advice execution (outInstructions()) : after () // Listener source: "guest system"
|
||||
{
|
||||
unsigned rDX = getCPU(tjp->that())->gen_reg[2].word.rx; // port number
|
||||
unsigned rAL = getCPU(tjp->that())->gen_reg[0].word.byte.rl; // data
|
||||
if (rDX == BOCHS_COM_PORT)
|
||||
fail::simulator.onGuestSystemEvent((char)rAL, rDX);
|
||||
fail::simulator.onGuestSystemListener((char)rAL, rDX);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ aspect IOPortCom {
|
||||
{
|
||||
unsigned rDX = getCPU(tjp->that())->gen_reg[2].word.rx; // port number
|
||||
unsigned char rAL = getCPU(tjp->that())->gen_reg[0].word.byte.rl; // data
|
||||
fail::simulator.onIOPortEvent(rAL, rDX, true);
|
||||
fail::simulator.onIOPortListener(rAL, rDX, true);
|
||||
}
|
||||
|
||||
pointcut inInstruction() = "% ...::bx_cpu_c::IN_ALDX(...)";
|
||||
@ -30,7 +30,7 @@ aspect IOPortCom {
|
||||
{
|
||||
unsigned rDX = getCPU(tjp->that())->gen_reg[2].word.rx; // port number
|
||||
unsigned char rAL = getCPU(tjp->that())->gen_reg[0].word.byte.rl; // data
|
||||
fail::simulator.onIOPortEvent(rAL, rDX, false);
|
||||
fail::simulator.onIOPortListener(rAL, rDX, false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -28,9 +28,9 @@ aspect Interrupt {
|
||||
unsigned vector = *(tjp->arg<0>());
|
||||
unsigned type = *(tjp->arg<1>());
|
||||
if (type == BX_EXTERNAL_INTERRUPT)
|
||||
fail::simulator.onInterruptEvent(vector, false);
|
||||
fail::simulator.onInterruptListener(vector, false);
|
||||
else if (type == BX_NMI)
|
||||
fail::simulator.onInterruptEvent(vector, true);
|
||||
fail::simulator.onInterruptListener(vector, true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ aspect Jump {
|
||||
advice execution (defJumpInstructions()) : around()
|
||||
{
|
||||
bxInstruction_c* pInstr = *(tjp->arg<0>()); // bxInstruction_c-object
|
||||
fail::simulator.onJumpEvent(true, pInstr->getIaOpcode());
|
||||
fail::simulator.onJumpListener(true, pInstr->getIaOpcode());
|
||||
/*
|
||||
JoinPoint::That* pThis = tjp->that();
|
||||
if(pThis == NULL)
|
||||
@ -77,7 +77,7 @@ aspect Jump {
|
||||
// Step-1: Modify one or more of the fxxxFlag according to the error you want to inject
|
||||
// (using pThis->set_XX(new_val))
|
||||
// Step-2: Call tjp->proceed();
|
||||
// Step-3: Eventually, unwind the changes of Step-1
|
||||
// Step-3: Listenerually, unwind the changes of Step-1
|
||||
//
|
||||
|
||||
// Example:
|
||||
@ -109,7 +109,7 @@ aspect Jump {
|
||||
advice execution (regJumpInstructions()) : around ()
|
||||
{
|
||||
bxInstruction_c* pInstr = *(tjp->arg<0>()); // bxInstruction_c-object
|
||||
fail::simulator.onJumpEvent(false, pInstr->getIaOpcode());
|
||||
fail::simulator.onJumpListener(false, pInstr->getIaOpcode());
|
||||
/*
|
||||
JoinPoint::That* pThis = tjp->that();
|
||||
|
||||
|
||||
@ -56,19 +56,19 @@ aspect MemAccess {
|
||||
// Fire a memory-write-event each time the guest system requests
|
||||
// to write data to RAM:
|
||||
//
|
||||
// Event source: "memory write access"
|
||||
// Listener source: "memory write access"
|
||||
//
|
||||
#ifdef CONFIG_EVENT_MEMWRITE
|
||||
advice execution (write_methods()) : after ()
|
||||
{
|
||||
fail::simulator.onMemoryAccessEvent(
|
||||
fail::simulator.onMemoryAccessListener(
|
||||
*(tjp->arg<1>()), sizeof(*(tjp->arg<2>())), true,
|
||||
getCPU(tjp->that())->prev_rip);
|
||||
}
|
||||
|
||||
advice execution (write_methods_RMW()) : after ()
|
||||
{
|
||||
fail::simulator.onMemoryAccessEvent(
|
||||
fail::simulator.onMemoryAccessListener(
|
||||
rmw_address, sizeof(*(tjp->arg<0>())), true,
|
||||
getCPU(tjp->that())->prev_rip);
|
||||
}
|
||||
@ -77,7 +77,7 @@ aspect MemAccess {
|
||||
{
|
||||
std::cerr << "WOOOOOT write_methods_new_stack" << std::endl;
|
||||
// TODO: Log-level?
|
||||
fail::simulator.onMemoryAccessEvent(
|
||||
fail::simulator.onMemoryAccessListener(
|
||||
*(tjp->arg<1>()), sizeof(*(tjp->arg<3>())), true,
|
||||
getCPU(tjp->that())->prev_rip);
|
||||
}
|
||||
@ -86,7 +86,7 @@ aspect MemAccess {
|
||||
{
|
||||
std::cerr << "WOOOOOT write_methods_new_stack_64" << std::endl;
|
||||
// TODO: Log-level?
|
||||
fail::simulator.onMemoryAccessEvent(
|
||||
fail::simulator.onMemoryAccessListener(
|
||||
*(tjp->arg<0>()), sizeof(*(tjp->arg<2>())), true,
|
||||
getCPU(tjp->that())->prev_rip);
|
||||
}
|
||||
@ -98,7 +98,7 @@ aspect MemAccess {
|
||||
// memory (e.g., to read vectors from the interrupt vector
|
||||
// table).
|
||||
/*
|
||||
fail::simulator.onMemoryAccessEvent(
|
||||
fail::simulator.onMemoryAccessListener(
|
||||
*(tjp->arg<0>()), sizeof(*(tjp->arg<1>())), true,
|
||||
getCPU(tjp->that())->prev_rip);
|
||||
*/
|
||||
@ -109,19 +109,19 @@ aspect MemAccess {
|
||||
// Fire a memory-read-event each time the guest system requests
|
||||
// to read data in RAM:
|
||||
//
|
||||
// Event source: "memory read access"
|
||||
// Listener source: "memory read access"
|
||||
//
|
||||
#ifdef CONFIG_EVENT_MEMREAD
|
||||
advice execution (read_methods()) : before ()
|
||||
{
|
||||
fail::simulator.onMemoryAccessEvent(
|
||||
fail::simulator.onMemoryAccessListener(
|
||||
*(tjp->arg<1>()), sizeof(*(tjp->result())), false,
|
||||
getCPU(tjp->that())->prev_rip);
|
||||
}
|
||||
|
||||
advice execution (read_methods_dqword()) : before ()
|
||||
{
|
||||
fail::simulator.onMemoryAccessEvent(
|
||||
fail::simulator.onMemoryAccessListener(
|
||||
*(tjp->arg<1>()), 16, false,
|
||||
getCPU(tjp->that())->prev_rip);
|
||||
}
|
||||
@ -131,7 +131,7 @@ aspect MemAccess {
|
||||
{
|
||||
rmw_address = *(tjp->arg<1>());
|
||||
#ifdef CONFIG_EVENT_MEMREAD
|
||||
fail::simulator.onMemoryAccessEvent(
|
||||
fail::simulator.onMemoryAccessListener(
|
||||
*(tjp->arg<1>()), sizeof(*(tjp->result())), false,
|
||||
getCPU(tjp->that())->prev_rip);
|
||||
#endif
|
||||
@ -145,7 +145,7 @@ aspect MemAccess {
|
||||
// memory (e.g., to read vectors from the interrupt vector
|
||||
// table).
|
||||
/*
|
||||
fail::simulator.onMemoryAccessEvent(
|
||||
fail::simulator.onMemoryAccessListener(
|
||||
*(tjp->arg<0>()), sizeof(*(tjp->result())), false,
|
||||
getCPU(tjp->that())->prev_rip);
|
||||
*/
|
||||
|
||||
@ -15,9 +15,9 @@ aspect Trap {
|
||||
|
||||
advice execution (exception_method()) : before ()
|
||||
{
|
||||
fail::simulator.onTrapEvent(*(tjp->arg<0>()));
|
||||
fail::simulator.onTrapListener(*(tjp->arg<0>()));
|
||||
// TODO: There are some different types of exceptions at cpu.h (line 265-281)
|
||||
// Which kind of a trap are these types?
|
||||
// Which kind of traps are these types?
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -84,12 +84,12 @@ void OVPController::onInstrPtrChanged(address_t instrPtr)
|
||||
// cerr << "instrPtr: 0x" << hex << instrPtr << " SP: 0x" << hex << m_Regs->getStackPointer() \
|
||||
// << " R0: 0x" << hex << r0 << " ST: 0x" << hex << st << endl;
|
||||
|
||||
// Check for active breakpoint-events:
|
||||
EventManager::iterator it = m_EvList.begin();
|
||||
// Check for active breakpoint-Listeners:
|
||||
ListenerManager::iterator it = m_EvList.begin();
|
||||
while (it != m_EvList.end()) {
|
||||
// FIXME: Performance verbessern (dazu muss entsprechend auch die Speicherung
|
||||
// in EventManager(.cc|.hpp) angepasst bzw. verbessert werden).
|
||||
BPSingleEvent* pEvBreakpt = dynamic_cast<BPSingleEvent*>(*it);
|
||||
// in ListenerManager(.cc|.hpp) angepasst bzw. verbessert werden).
|
||||
BPSingleListener* pEvBreakpt = dynamic_cast<BPSingleListener*>(*it);
|
||||
if (pEvBreakpt && (instrPtr == pEvBreakpt->getWatchInstructionPointer() ||
|
||||
pEvBreakpt->getWatchInstructionPointer() == ANY_ADDR)) {
|
||||
pEvBreakpt->setTriggerInstructionPointer(instrPtr);
|
||||
@ -98,7 +98,7 @@ void OVPController::onInstrPtrChanged(address_t instrPtr)
|
||||
// makeActive()):
|
||||
continue; // -> skip iterator increment
|
||||
}
|
||||
BPRangeEvent* pEvRange = dynamic_cast<BPRangeEvent*>(*it);
|
||||
BPRangeListener* pEvRange = dynamic_cast<BPRangeListener*>(*it);
|
||||
if (pEvRange && pEvRange->isMatching(instrPtr)) {
|
||||
pEvBreakpt->setTriggerInstructionPointer(instrPtr);
|
||||
it = m_EvList.makeActive(it);
|
||||
@ -106,7 +106,7 @@ void OVPController::onInstrPtrChanged(address_t instrPtr)
|
||||
}
|
||||
it++;
|
||||
}
|
||||
m_EvList.fireActiveEvents();
|
||||
m_EvList.fireActiveListeners();
|
||||
}
|
||||
|
||||
void OVPController::save(const string& path)
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "../SimulatorController.hpp"
|
||||
#include "../Event.hpp"
|
||||
#include "../Listener.hpp"
|
||||
#include "../Register.hpp"
|
||||
#include "ovp/OVPPlatform.hpp"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user