package jde.debugger; import jde.debugger.spec.*; import java.util.*; import com.sun.jdi.*; import com.sun.jdi.event.*; import com.sun.jdi.request.*; import com.sun.jdi.connect.*; import jde.debugger.LispForm; /** * EventHandler.java *

* Each debugee VM has an event handler thread on the jdebug side * associated with it that receive all the events from the debugee * vm. In turn, the event handler thread passes the events on to the * jde, indicating if the vm/current thread was suspended. *

* Created: Tue Jul 6 14:08:44 1999 * * @author Amit Kumar * @since 0.1 */ public class EventHandler implements Runnable, Protocol { /** Are we connected to the VM? */ boolean connected = true; /** The process for which we're the event handler */ final DebuggeeProcess proc; /** The ID of the process */ final Integer procID; final JDE jde = JDE.getJDE(); /** * My own thread. Used when we want to suspend ourselves or some such * weird stuff :-) */ final Thread thread; /** Keeping track of if the thread is over yet or not */ boolean completed = false; /** * Used by the event handlers to indicate if the app should be * resumed after the event occured. */ boolean resumeApp; public EventHandler(DebuggeeProcess proc) { /* the q gets all the events from the vm */ this.proc = proc; this.procID = proc.getId(); this.thread = new Thread(this, "Event Handler for App #"+procID); this.thread.start(); } public void shutdown() { connected = false; thread.interrupt(); while (!completed) { try {wait();} catch (InterruptedException ex) {} } } /** * The thread reads an eventset at a time from the application queue, * and processes it. Essentially, one by one, each event of the eventset * is sent to the corresponding "handler", and if at least one of them * returns a non-null value, we pass on the eventset (and the values * returned by the handlers) to jde. Otherwise, we just resume the vm * (regardless of the suspend policy) and wait for the next eventset. *

* This is the syntax of the event set: *

   * (JDE_BUG_EVENTSET suspend-state thread [event-string]+)
   * 
* * */ public void run() { EventQueue queue = proc.getEventQueue(); /* only need to run while we're connected */ while (connected) { try { EventSet eventSet = queue.remove(); resumeApp = true; List events = new ArrayList(); EventIterator it = eventSet.eventIterator(); // System.out.println("Suspend policy of eventset: "+((eventSet.suspendPolicy() == EventRequest.SUSPEND_NONE)?"none":"not none")); while (it.hasNext()) { // System.out.println("An event"); LispForm sendLispForm = handleEvent(it.nextEvent()); // if the handler thinks the user doesn't deserve // the event, it'll return null. another microsoftism // in the code... :-( if (sendLispForm != null) { events.add(sendLispForm); } } // if no event wants to inform itself to the user, what // are we doing anyway? if (events.size() == 0) { eventSet.resume(); continue; } String suspendStateString; if (resumeApp) { // this resume does a vm resume or a thread resume // depending on how it was suspended suspendStateString = "none"; eventSet.resume(); } else { switch(eventSet.suspendPolicy()) { case EventRequest.SUSPEND_ALL: { suspendStateString = "all"; break; } case EventRequest.SUSPEND_EVENT_THREAD: { suspendStateString = "thread"; break; } case EventRequest.SUSPEND_NONE: { suspendStateString = "none"; eventSet.resume(); break; } default: { suspendStateString = "invalid"; break; } } } String eventSetString = "\""+suspendStateString+"\""; ThreadReference eventThread = getCurrentThread(eventSet); if (eventThread == null) { eventSetString += " nil"; } else { eventSetString += BR + Rep.getThreadRep(eventThread, proc.getStore()); } Iterator iter = events.iterator(); while (iter.hasNext()) { eventSetString += BR +iter.next(); } // finally, we send the events to The Man. (or woman...) jde.signal(procID, EVENTSET, new LispForm(eventSetString)); } catch (InterruptedException ex) { // Debug.printIf(ex); // used by shutdown } catch (VMDisconnectedException ex) { Debug.printIf(ex); handleDisconnectedException(); } } synchronized (this) { completed = true; notifyAll(); } } /** * Handles the events that happened * * @param event One of the events in the event set. * @return A LispForm that indicates what should be sent to jde. This * should be of the form "(list jde-dbo-EVENT-event [args])". Each * of the functions called herein should return a LispForm of the above * form. */ private LispForm handleEvent(Event event) { if (event instanceof BreakpointEvent) { return breakpointEvent((BreakpointEvent)event); } else if (event instanceof StepEvent) { return stepEvent((StepEvent)event); } else if (event instanceof WatchpointEvent) { return watchpointEvent((WatchpointEvent)event); } else if (event instanceof ExceptionEvent) { return exceptionEvent((ExceptionEvent)event); } else if (event instanceof ThreadStartEvent) { return threadStartEvent((ThreadStartEvent)event); } else if (event instanceof ThreadDeathEvent) { return threadDeathEvent((ThreadDeathEvent)event); } else if (event instanceof MethodEntryEvent) { return methodEntryEvent((MethodEntryEvent)event); } else if (event instanceof MethodExitEvent) { return methodExitEvent((MethodExitEvent)event); } else if (event instanceof ClassPrepareEvent) { return classPrepareEvent((ClassPrepareEvent)event); } else if (event instanceof VMStartEvent) { return vmStartEvent((VMStartEvent)event); } else if (event instanceof VMDeathEvent) { return vmDeathEvent((VMDeathEvent)event); } else if (event instanceof VMDisconnectEvent) { return vmDisconnectEvent((VMDisconnectEvent)event); } else { return otherEvent(event); } } /** * Duh... we don't recognize this event... or, we choose not to do * anything about it */ private LispForm otherEvent(Event event) { resumeApp &= true; return new LispForm("(list '"+EVENT_OTHER+")"); } /** * What we should do if we get disconnected while we're doing something * else. */ private void handleDisconnectedException() { // disconnected while handling some other event. flush queue // and deal with disconnectEvent and deathEvents EventQueue queue = proc.getEventQueue(); while (connected) { try { EventSet eventSet = queue.remove(); EventIterator iter = eventSet.eventIterator(); while (iter.hasNext()) { Event evt = (Event)iter.next(); if (evt instanceof VMDeathEvent) { vmDeathEvent(evt); } else if (evt instanceof VMDisconnectEvent) { vmDisconnectEvent(evt); } } } catch (InterruptedException ex) { Debug.printIf(ex); // ignore } } } /** * Get the current thread of this event set. It's not necessary for * an event set to have a thread associated with it: in those cases * just return null. * * @param eventSet The event set that occured * @return null if there is no thread associated, otherwise * the ThreadReference of the Thread */ private ThreadReference getCurrentThread(EventSet eventSet) { ThreadReference thread; if (eventSet.size() > 0) { /* * If any event in the set has a thread associated with it, * they all will, so just grab the first one. */ Event event = (Event)eventSet.iterator().next(); // Is there a better way? thread = getEventThread(event); } else { thread = null; } return thread; } /** * Black magic to divine the ThreadReference of the * event. Question: can we use "Black magic" and "divine" in the same * sentence? * * @param event An event from the event set: any event will do * @return The ThreadReference of the thread */ private ThreadReference getEventThread(Event event) { if (event instanceof ClassPrepareEvent) { return ((ClassPrepareEvent)event).thread(); } else if (event instanceof LocatableEvent) { return ((LocatableEvent)event).thread(); } else if (event instanceof ThreadStartEvent) { return ((ThreadStartEvent)event).thread(); } else if (event instanceof ThreadDeathEvent) { return ((ThreadDeathEvent)event).thread(); } else if (event instanceof VMStartEvent) { return ((VMStartEvent)event).thread(); } else { return null; } } /** * For events with corresponding specs (eg. watchpoints), checks to see * if the event thread matches the thread constraint in the spec. *

* * Syntax: *

   * nil                              # If no such constraint
   *                                  #   was found in the spec
   * "Error mesg"                     # Constraint found, but error
   *                                  #   during evaluation
   * (list "on_thread_id" threadID)   # Constraint found, satisfied
   * (list "on_thread_name" "name")
   * 
* * Comments: * */ private String threadMatch(Event event) { Object thread = event.request().getProperty(EventRequestSpec.threadKey); if (thread == null) { return "nil"; } else if (thread instanceof Long) { ThreadReference t = getEventThread(event); if (t.uniqueID() == ((Long)thread).longValue()) { return "(list \"on_thread_id\" "+t.uniqueID()+")"; } else { return null; } } else if (thread instanceof String) { ThreadReference tRef = proc.getThread(thread.toString()); ThreadReference t = getEventThread(event); if (t.equals(tRef)) { return "(list \"on_thread_name\"" +" \""+thread.toString()+"\")"; } else { return null; } } else { return "\"Error matching thread\""; } } /** * For events with corresponding specs (eg. watchpoints), evaluates the * expression stored in the spec, if any, to check if the event is * interesting to the user or not. The constraint is thus the expression. *

* * Syntax: *

   * nil                              # If no such constraint
   *                                  #   was found in the spec
   * "Error mesg"                     # Constraint found, but error
   *                                  #   during evaluation
   * (list "expression")              # Constraint found, satisfied
   * 
* * Comments: * */ private String expressionSuccess(Event event) { Object exprObject = event.request().getProperty(EventRequestSpec.expressionKey); if (exprObject != null) { String expr = exprObject.toString(); try { StackFrame frame = getEventThread(event).frame(0); Value val = Etc.evaluate(expr, frame); if (!val.type().name().equals("boolean")) { return "\"Expression evaluates to non-boolean\""; } else { BooleanValue boolValue = (BooleanValue)val; if (boolValue.value() == true) { return "(list \""+expr+"\")"; } else { return null; } } } catch (Exception ex) { Debug.printIf(ex); return "\"Expression didn't evaluate correctly\""; } } return "nil"; } /** * For watchpoints, when the object-id specified during the command * should match the object id of the event. *

* * Syntax: *

   * nil                              # if no such constraint was in spec
   * "Error mesg"                     # Constraint found, error in eval.
   * (list object-id)                 # Constraint found, satisfied.
   * 
* * Comments: * */ private String objectIDMatches(WatchpointEvent event) { String objectIDString; Object idObject = event.request().getProperty(WatchpointSpec.objectIDKey); if (idObject == null) { return "nil"; } else if (idObject instanceof Long) { Long id = (Long)idObject; if (event.object() == null) { return "(list "+id+")"; } else if (event.object().uniqueID() == id.longValue()) { return "(list "+id+")"; } else { return null; } } else { return "\"Object ID was not a Long\""; } } /** * A Breakpoint was hit. We check (based on information in the event * request; put in when the spec was resolved; that's sent along with * the event) if the user wants to know about this event. If so, we * return a lispform, else return null. *

* * Syntax: *

   * (list {@link Protocol#EVENT_BREAKPOINT_HIT breakpoint-hit-function} specID {@link Rep#getLocationRep(Location) location} {@link #threadMatch thread-string} {@link #expressionSuccess expression-string})
   * 
* * Comments: * */ private LispForm breakpointEvent(BreakpointEvent event) { Long specID = ((EventRequestSpec)event.request().getProperty(EventRequestSpec.specPropertyKey)).getID(); // check if the current thread is ok. String threadString = threadMatch(event); if (threadString == null) { resumeApp &= true; return null; } // check if the expression matches String exprString = expressionSuccess(event); if (exprString == null) { resumeApp &= true; return null; } resumeApp &= false; return new LispForm("(list '"+EVENT_BREAKPOINT_HIT +" "+specID + BR +Rep.getLocationRep(event.location()) +" "+threadString +" "+exprString+")"); } /** * A step event occured. *

* * Syntax: *

   * (list {@link Protocol#EVENT_STEP_COMPLETED step-function} {@link Rep#getLocationRep(Location) location-string})
   * 
*/ private LispForm stepEvent(StepEvent event) { resumeApp &= false; return new LispForm("(list '" +EVENT_STEP_COMPLETED +" "+Rep.getLocationRep(event.location()) +")"); } /** * A Watchpoint occured. We check (based on information in the event * request; put in when the spec was resolved; that's sent along with * the event) if the user wants to know about this event. If so, we * return a lispform, else return null. *

* * Syntax: *

   * (list {@link Protocol#EVENT_WATCHPOINT_HIT watchpoint-hit-function} specID {@link Rep#getObjectRep(ObjectReference,ObjectStore) object-on-which-hit} {@link Rep#getFieldValueRep(Field, Value, ObjectStore) field-and-value} {@link Rep#getLocationRep(Location) location}
   *       {@link #objectIDMatches object-id-string} {@link #threadMatch thread-string} {@link #expressionSuccess expression-string})
   * 
* * Comments: * */ private LispForm watchpointEvent(WatchpointEvent event) { Long specID = ((EventRequestSpec)event.request().getProperty(EventRequestSpec.specPropertyKey)).getID(); // System.out.println("Suspend policy of WatchpointEvent: "+((event.request().suspendPolicy() == EventRequest.SUSPEND_NONE)?"none":"not none")); // check if the current thread is ok. String threadString = threadMatch(event); if (threadString == null) { resumeApp &= true; return null; } // check if the expression matches String exprString = expressionSuccess(event); if (exprString == null) { resumeApp &= true; return null; } // check if the object id (if specified) matches the event object String objectIDString = objectIDMatches(event); if (objectIDString == null) { resumeApp &= true; return null; } String fieldValueString = Rep.getFieldValueRep(event.field(), event.valueCurrent(), proc.getStore()).toString(); String objectString = Rep.getObjectRep(event.object(), proc.getStore()).toString(); resumeApp &= false; return new LispForm("(list '"+EVENT_WATCHPOINT_HIT +" "+specID + BR +objectString + BR +fieldValueString + BR +Rep.getLocationRep(event.location()) + BR +objectIDString +" "+threadString +" "+exprString+")"); } /** * An exception event occured. Depending on the command, it could occur * for both caught and uncaught exceptions. *

* * Syntax: *

   * (list {@link Protocol#EVENT_EXCEPTION exception-function} specID {@link Rep#getObjectRep(ObjectReference,ObjectStore) exception} {@link #threadMatch thread-string})
   * 
* * Comments: * */ private LispForm exceptionEvent(ExceptionEvent event) { if (event.request() == null) { resumeApp &= true; return null; } Long specID = ((EventRequestSpec)event.request().getProperty(EventRequestSpec.specPropertyKey)).getID(); ExceptionRequest request = (ExceptionRequest)event.request(); // check if the current thread is ok. String threadString = threadMatch(event); if (threadString == null) { resumeApp &= true; return null; } resumeApp &= false; return new LispForm("(list '" +EVENT_EXCEPTION +" "+specID + BR +Rep.getObjectRep(event.exception(), proc.getStore()) + BR +threadString+")"); } /** * A method was entered. *

* * Syntax: *

   * (list {@link Protocol#EVENT_METHOD_ENTRY method-entry-function} {@link Rep#getMethodRep method})
   * 
*/ private LispForm methodEntryEvent(MethodEntryEvent event) { resumeApp &= false; return new LispForm("(list '" + EVENT_METHOD_ENTRY + BR +Rep.getMethodRep(event.method()) +")"); } /** * A method was exit. *

* * Syntax: *

   * (list {@link Protocol#EVENT_METHOD_EXIT method-exit-function} {@link Rep#getMethodRep method})
   * 
*/ private LispForm methodExitEvent(MethodExitEvent event) { resumeApp &= false; return new LispForm("(list '" + EVENT_METHOD_EXIT + BR +Rep.getMethodRep(event.method()) +")"); } /** * A thread started *

* * Syntax: *

   * (list {@link Protocol#EVENT_THREAD_START thread-start-function} {@link Rep#getThreadRep thread})
   * 
*/ private LispForm threadStartEvent(ThreadStartEvent event) { resumeApp &= false; return new LispForm("(list '" + EVENT_THREAD_START + BR +Rep.getThreadRep(event.thread(), proc.getStore()) +")"); } /** * A thread died. *

* * Syntax: *

   * (list {@link Protocol#EVENT_THREAD_DEATH thread-death-function} {@link Rep#getThreadRep thread})
   * 
*/ private LispForm threadDeathEvent(ThreadDeathEvent event) { resumeApp &= false; return new LispForm("(list '" + EVENT_THREAD_DEATH + BR +Rep.getThreadRep(event.thread(), proc.getStore()) +")"); } /** * A class was prepared. The user might not have even requested for * this event: we set it up by default in {@link Application#Application} * because we need it for resolution of specs. *

* If a user also requests for this event, a particular property is * likely not set, and that is how we know that we're supposed to * handle the event for the user. *

* * Syntax: *

   * (list {@link Protocol#EVENT_CLASS_PREPARE class-prepare-function} reference-type)
   * 
*/ private LispForm classPrepareEvent(ClassPrepareEvent event) { proc.resolve(event.referenceType()); // now find out if this event was also requested by the user. // it will be, if the "default" property does NOT exists in the // corresponding request. EventRequest request = event.request(); if (request.getProperty("default") == null) { resumeApp &= false; return new LispForm("(list '"+EVENT_CLASS_PREPARE +" \""+event.referenceType().name()+"\")"); } else { resumeApp &= true; return null; } } /** * A class was unloaded. *

* * Syntax: *

   * (list {@link Protocol#EVENT_CLASS_UNLOAD class-unload-function} reference-type)
   * 
*/ private LispForm classUnloadEvent(ClassUnloadEvent event) { resumeApp &= false; return new LispForm("(list '"+EVENT_CLASS_UNLOAD +" \""+event.className()+"\")"); } private LispForm vmStartEvent(Event event) { resumeApp &= false; return new LispForm("(list '"+EVENT_VM_START+")"); } private LispForm vmDeathEvent(Event event) { resumeApp &= true; return new LispForm("(list '"+EVENT_VM_DEATH+")"); } private LispForm vmDisconnectEvent(Event event) { connected = false; resumeApp &= true; proc.shutdown(); return new LispForm("(list '"+EVENT_VM_DISCONNECT+")"); } } // EventHandler