package xtc.lang.blink; import xtc.lang.blink.Blink.DebugerControlStatus; import xtc.lang.blink.Event.JavaBreakPointHitEvent; import xtc.lang.blink.Event.JavaPauseEvent; import xtc.lang.blink.Event.JavaExceptionEvent; import xtc.lang.blink.Event.JavaLoadLibraryEvent; import xtc.lang.blink.Event.DeathEvent; import xtc.lang.blink.Event.NativeJNIWarningEvent; import xtc.lang.blink.Event.SubDebuggerEvent; import xtc.lang.blink.Event.NativeBreakPointHitEvent; import xtc.lang.blink.Event.NativeSignalEvent; import xtc.lang.blink.Event.UserCommandEvent; import xtc.lang.blink.Event.SessionFinishRequestEvent; import xtc.tree.GNode; /** * The Blink event loop. * * @author Byeongcheol Lee */ public class EventLoop { /** The Blink debugger. */ private final Blink dbg; /** The Blink command interpreter. */ final CommandInterpreter interpreter; /** keeping state of sub systems. */ private boolean jvmFinished = false; private boolean jdbFinisned = false; private boolean gdbFinished = false; /** * Constructor. * * @param dbg The Blink debugger. */ EventLoop(Blink dbg) { this.dbg = dbg; this.interpreter = new CommandInterpreter(dbg, dbg.breakpointManager); } /** * Run the main event loop. Wait until an event is available in the event * queue, and, if the event is ready, dequeue this event. Then, dispatch the * event to the corresponding handler, depending on the event type. This * command loop may return if any micro debuggers such as jdb and gdb * terminates or if the user asks the termination by typing "exit" command. * @ */ void main() { boolean exitRequested = false; while (!exitRequested) { Event e = dbg.dequeEvent(); if (e instanceof DeathEvent) { dbg.exit(); assert false : "unreachable"; break; } switch(e.consumer) { case BlinkController: if (e instanceof UserCommandEvent) { dispatch((UserCommandEvent) e); } else if (e instanceof SubDebuggerEvent) { dispatch((SubDebuggerEvent) e); } else if (e instanceof SessionFinishRequestEvent) { exitRequested = true; } break; case JavaDebugger: dbg.jdb.dispatch(e); break; case NativerDebugger: dbg.ndb.dispatch(e); break; } } } /** * Run a command, and repeat event dispatch to the handler until the handler * is satisfied. * * @param handler The message handler for the replier. * @return The response from the reply handler. */ public static Object subLoop(Blink dbg, ReplyHandler handler) { // wait until the replyHandler is satisfied. boolean satisfied = false; while (!satisfied) { Event e = dbg.dequeEvent(); if (e instanceof DeathEvent) { dbg.exit(); assert false : "unreachable"; break; } switch(e.consumer) { case BlinkController: if (e instanceof UserCommandEvent) { dbg.eventLoop.dispatch((UserCommandEvent) e); } else { satisfied = handler.dispatch(e); } break; case JavaDebugger: dbg.jdb.dispatch(e); break; case NativerDebugger: dbg.ndb.dispatch(e); break; } } assert satisfied == true; return handler.getResult(); } /** * Dispatch a user command event. * * @param e The event. */ void dispatch(UserCommandEvent e) { String line = e.getCommandLine(); executeBlinkCommand(line); dbg.showPrompt(); } /** * Dispatch an asynchronous micro DebuggerUserdebugger event. * * @param e The event. * @ */ private void dispatch(SubDebuggerEvent e) { if (e instanceof DeathEvent) { dispatch((DeathEvent) e); } else if (e instanceof JavaLoadLibraryEvent) { dispatch((JavaLoadLibraryEvent)e); } else if (e instanceof NativeBreakPointHitEvent) { dispatch((NativeBreakPointHitEvent)e); } else if (e instanceof NativeJNIWarningEvent) { dispatch((NativeJNIWarningEvent)e); } else if (e instanceof JavaPauseEvent) { dispatch((JavaPauseEvent)e); } else if (e instanceof NativeSignalEvent) { dispatch((NativeSignalEvent)e); } else { assert false; } } /** * Dispatch an asynchronous component debugger death event. * * @param e The event. */ private void dispatch(DeathEvent e) { if (e.getSource() == dbg.jvm) { assert jvmFinished == false : "no double death!"; jvmFinished = true; } else if (e.getSource() == dbg.jdb) { assert jdbFinisned == false : "no double death!"; jdbFinisned = true; } else if (e.getSource() == dbg.ndb) { assert gdbFinished == false; gdbFinished = true; } // check whether or not to finish the debugging session. if (jvmFinished && jdbFinisned && (!dbg.IsNativeDebuggerAttached() || gdbFinished)) { dbg.enqueEvent(new SessionFinishRequestEvent("Application finished")); } } /** * Run a macro user command. * * @param command The command. */ void executeBlinkCommand(String command) { // try parsing and executing the command line. final String language = dbg.getCurrentLanguageContext(); final Object astOrMsg = Utilities.debuggerParseAndAnalyze(language, command); if (astOrMsg instanceof GNode) { final GNode ast = (GNode) astOrMsg; interpreter.dispatch(ast); } else { dbg.err((String) astOrMsg); } } /** * The jdb hits the System.loadLibrary event. * * @param slave The slave process that gets the System.loadLibrary event. */ private synchronized void dispatch(JavaLoadLibraryEvent e) { assert dbg.getDebugControlStatus() == DebugerControlStatus.NONE; dbg.changeDebugControlStatus(DebugerControlStatus.JDB); dbg.jdb.resetLoadLibraryEvent(); dbg.jdb.prepareLoadLibrary(); if (dbg.ensureDebugAgent()) { if (dbg.breakpointManager.hasDeferredNativeBreakpoint()) { dbg.breakpointManager.handleDeferredNativeBreakPoint(); } } else { dbg.jdb.setLoadLibraryEvent(); } dbg.ensureJDBContext(); dbg.jdb.cont(); dbg.changeDebugControlStatus(DebugerControlStatus.NONE); } /** * The Java break point hit notification. * * @param classAndMethod The class and name pair. * @param line The line number. * @param sourceLine The source line. */ private void dispatch(JavaPauseEvent e) { assert dbg.getDebugControlStatus() == DebugerControlStatus.NONE; dbg.changeDebugControlStatus(DebugerControlStatus.JDB); reportEvent(dbg, e); dbg.showPrompt(); } /** * @param e The native break point hit event. */ private void dispatch(NativeBreakPointHitEvent e) { assert dbg.getDebugControlStatus() == DebugerControlStatus.NONE; dbg.changeDebugControlStatus(DebugerControlStatus.GDB); reportEvent(dbg, e); dbg.showPrompt(); } private void dispatch(NativeSignalEvent e) { assert dbg.getDebugControlStatus() == DebugerControlStatus.NONE; dbg.changeDebugControlStatus(DebugerControlStatus.GDB); int s = Integer.valueOf(dbg.ndb.eval(null, "(int)bda_tls_state")); if (s != 0) { String mode = dbg.ndb.eval(null, "bda_tls_state->mode"); if ("JVM".equals(mode)) { dbg.cont(); return; } } reportEvent(dbg, e); dbg.showPrompt(); } /** * Report there is potential JNI function misuse that might crash * the JVM. * * @param e The native JNI warning event. */ private void dispatch(NativeJNIWarningEvent e) { dbg.changeDebugControlStatus(DebugerControlStatus.GDB); dbg.out("JNI warning: %s\n", e.getMessage()); dbg.showPrompt(); } public static void reportEvent(Blink dbg, JavaPauseEvent e) { if (e instanceof JavaBreakPointHitEvent) { BreakPointManager bpManger = dbg.breakpointManager; int bpid = bpManger.findJavaBreakpoint(e.getClassName(), e.getMethodName(), e.getLineNumber()); String bpidMsg = bpid == BreakPointManager.INVALID_BREAKPOINT_ID ? "?" : String.valueOf(bpid); dbg.out("Breakpoint %s: thread=%s, %s.%s(), line=%d, bci=%d\n%s", bpidMsg, e.getThreadName(), e.getClassName(), e.getMethodName(), e.getLineNumber(), e.getBcindex(), e.getMessage()); } else if (e instanceof JavaExceptionEvent) { JavaExceptionEvent je = (JavaExceptionEvent)e; dbg.out("Java exception occured: %s thread=%s, %s.%s(), line=%d bci=%d\n%s", je.getExceptionClass(), je.getThreadName(), je.getClassName(), je.getMethodName(), e.getLineNumber(), e.getBcindex(), e.getMessage()); } } public static void reportEvent(Blink dbg, NativeBreakPointHitEvent e) { BreakPointManager bpManger = dbg.breakpointManager; int bpid = bpManger.findNativeBreakpoint(e.getDebuggerBreakpointID()); String bpidMsg = bpid == BreakPointManager.INVALID_BREAKPOINT_ID ? "?" : String.valueOf(bpid); dbg.out("Breakpoint %s: %s\n", bpidMsg, e.getMessage()); } public static void reportEvent(Blink dbg, NativeSignalEvent e) { dbg.out("Signal received: %s\n", e.signal); } /** * A reply handler for the micro debugger. This handler takes and parses a * multiple number of events from the micro debugger until some condition is * satisfied. If the condition is satisfied, the dispatchMessage will record * some summary of the received events. */ abstract static class ReplyHandler { /** The result for the reply. */ protected Object result; /** * @param result The result of handling the reply. */ protected void setResult(Object result) { assert this.result == null : "the result is set only once"; this.result = result; } /** * @return The result object */ public Object getResult() { return result; } /** * Takes an event and consider the previous events to see some waiting * condition is satisfied. If this method returns null, * * @param e The event. * @return true if some condition is satisfied. * @ */ abstract boolean dispatch(Event e) ; } }