package xtc.lang.blink; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; import java.util.concurrent.LinkedBlockingQueue; import static java.lang.String.format; import xtc.lang.blink.CallStack.JavaCallFrame; import xtc.lang.blink.Event.DummyCallCompletionEvent; import xtc.lang.blink.Event.J2CBreakPointHitEvent; import xtc.lang.blink.Event.Java2NativeCallEvent; import xtc.lang.blink.Event.Java2NativeCompletionEvent; import xtc.lang.blink.Event.Java2NativeReturnEvent; import xtc.lang.blink.Event.JavaStepCompletionEvent; import xtc.lang.blink.Event.LanguageTransitionEvent; import xtc.lang.blink.Event.Native2JavaCallEvent; import xtc.lang.blink.Event.Native2JavaCompletionEvent; import xtc.lang.blink.Event.Native2JavaReturnEvent; import xtc.lang.blink.Event.NativeBreakPointHitEvent; import xtc.lang.blink.Event.JavaBreakPointHitEvent; import xtc.lang.blink.Event.NativeStepCompletionEvent; import xtc.lang.blink.Event.RawTextMessageEvent; import xtc.lang.blink.JavaDebugger.InitializedEvent; import xtc.lang.blink.JavaDebugger.ListenAddressEvent; import xtc.lang.blink.NativeDebugger.LanguageTransitionEventType; import xtc.lang.blink.SymbolMapper.SourceFileAndLine; import xtc.lang.blink.agent.AgentNativeDeclaration; import xtc.lang.blink.EventLoop.ReplyHandler; import xtc.lang.blink.EventUtil.EventReplyHandler; import xtc.lang.blink.EventUtil.ConjunctiveReplyHandler; import xtc.lang.blink.EventUtil.DeathReplyHandler; import xtc.lang.blink.EventUtil.J2CCompletionEventHandler; import xtc.lang.blink.EventUtil.EventReplyHandler.EventFilter; import static xtc.lang.blink.agent.AgentJavaDeclaration.*; /** * The Blink debugger for the Java/C mixed mode source level debugging. * * @author Byeongcheol Lee */ public class Blink implements AgentNativeDeclaration { /** The debugger control status. */ enum DebugerControlStatus { NONE, JDB, GDB, JDB_IN_GDB, GDB_IN_JDB, } /** * Prints a usage message to the user and shows what is wrong in the command * line arguments. Then, terminates the Blink debugger with an error code * (-1). * * @param msg The message. */ private static void usage(String msg) { StringBuffer buf = new StringBuffer(); if (msg != null && msg.length() > 0) { buf.append(msg).append("\n\n"); } String usage = "Usage: xtc.lang.blink.Blink [options] CLASS [arguments]\n" + "Blink options:\n" + "\t-help\n" + "\t-jniassert\n" + "\t-agentpath\n" +'\n' + "options forwarded to JDB:\n" + "\t-sourcepath <directories separated by \":\">\n" + "\t-dbgtrace\n" +'\n' + "options forwarded to debuggee JVM:\n" + "\t-v -verbose[:class|gc|jni]\n" + "\t-D<name>=<value> system property\n" + "\t-classpath <directories separated by \":\">\n" + "\t-X<option>\n" +'\n' + "environment variables:\n" + "\tOSTYPE one of linux-gnu, mingw, and win32\n" + "\tJAVA_DEV_ROOT xtc installation path\n" ; buf.append(usage); System.out.println(buf.toString()); System.exit(-1); } /** * Print the help message for the Blink command. */ final void help() { String msg = "\n" + "help print help\n" + "exit exit the Blink debugger\n" + "run start the program run\n" + "\n" + "break [file:line] add a break point e.g.) break Main.jni:9\n" + "stop at <classid>:<line> add a break point e.g.) stop at Main:15\n" + "stop in <classid>:<method> add a break point e.g.) stop in Main:main\n" + "info break list break points\n" + "delete [n] delete a break/watch point with its id [n].\n" + "\n" + "where dump stack trace\n" + "up [n] select n frames up\n" + "down [n] select n frames down\n" + "list print source code.\n" + "locals print local variables in selected frame\n" + "print <jexpr> print Jeannie expression\n" + "\n" + "continue coninue running.\n" + "step execute until another line reached\n" + "next execute the next line, including function calls\n"; out("%s", msg); } /** * The main method for the Blink debugger. * * @param args The command line arguments. */ public static void main(String[] args) { InternalOption debuggerOptions = new InternalOption(); List<String> sbJVMOptions = new LinkedList<String>(); List<String> sbJDBOptions = new LinkedList<String>(); StringBuffer sbGDBOPtions = new StringBuffer(); String mainClass = null; List<String> sbMainOptions = new LinkedList<String>(); // parse arguments for (int i = 0; i < args.length; i++) { String arg = args[i]; // [options] CLASS if (mainClass == null) { if (arg.equals("-help")) { usage(""); } else if (arg.equals("-jniassert")) { debuggerOptions.setJniCheck(true); //jdb options } else if (arg.equals("-agentpath")) { String agentPath = args[++i]; debuggerOptions.setAgentPath(agentPath); } else if (arg.equals("-sourcepath")) { if ((i + 1) >= args.length) usage("Please, specify path after -sourcepath."); String argPath = args[++i]; sbJDBOptions.add(arg); sbJDBOptions.add(argPath); } else if (arg.equals("-dbgtrace")) { sbJDBOptions.add(arg); //jvm options } else if (arg.equals("-classpath")) { if ((i + 1) >= args.length) usage("Please, specify path after -classpath."); String argPath = args[++i]; sbJVMOptions.add(arg); sbJVMOptions.add(argPath); } else if (arg.equals("-v") || Pattern.matches("-verbose(:(class|gc|jni))?", arg)) { sbJVMOptions.add(arg); } else if (arg.matches("-X.+")) { sbJVMOptions.add(arg); } else if (arg.matches("-D[^=]+=.*")) { sbJVMOptions.add(arg); }else { mainClass = arg; } } else { // app options sbMainOptions.add(arg); } } // check arguments if (mainClass == null) { usage("Please, specify the main CLASS name."); } // build jvm arguments sbJVMOptions.add(mainClass); for(String a:sbMainOptions) { sbJVMOptions.add(a); } //now actually launch the Blink. try { Blink debugger = new Blink(mainClass, sbJVMOptions.toArray(new String[0]), sbJDBOptions.toArray(new String[0]), sbGDBOPtions.toString(), debuggerOptions); debugger.startSession(); debugger.eventLoop.main(); } catch (Exception e) { e.printStackTrace(); } System.exit(0); } /** Ensure the JAVA_DEV_ROOT directory is available. */ private static String ensureJavaDevRoot() { String java_dev_root= System.getenv("JAVA_DEV_ROOT"); if (java_dev_root == null || java_dev_root.equals("")) { usage("JAVA_DEV_ROOT environment variable is not set"); assert false:"not reachable"; } if (!new File(java_dev_root).isDirectory()) { usage(format("JAVA_DEV_ROOT(=%s) is expected to be directory", java_dev_root)); assert false:"not reachable"; } return java_dev_root; } /** Ensure the agent exists and return its full path. */ String ensureAgentLibraryPath() { String apath = options.getAgentPath(); if (apath != null && new File(apath).exists()) { return apath; } String java_dev_root = ensureJavaDevRoot(); String libpath = format("%s%sbin", java_dev_root, java.io.File.separator); String agentPath; String osType=System.getProperty("OSTYPE"); if ("linux-gnu".equals(osType)) { agentPath = format("%s%slib%s.so", libpath, java.io.File.separator, BDA_SHARED_LIBRARY_NAME); } else if ("win32".equals(osType)) { agentPath = format("%s%s%s.dll", libpath, java.io.File.separator, BDA_SHARED_LIBRARY_NAME); } else if ("mingw".equals(osType)) { agentPath = format("%s%s%s_mingw.dll", libpath, java.io.File.separator, BDA_SHARED_LIBRARY_NAME); } else { agentPath = format("%s%slib%s.so", libpath, java.io.File.separator, BDA_SHARED_LIBRARY_NAME); } if (new File(libpath).exists()) { return agentPath; } else { usage(format("can not find Blink agent native library in %s/bin",java_dev_root)); return null; } } /** * check if the Blink run jdb process, and create Blink's jdb controller. If * the Blink can not correctly create the jdb process, terminate the current * debugging session. */ private static JavaDebugger ensureJavaDebugger(Blink dbg) { try { Process p = Runtime.getRuntime().exec("jdb -version"); BufferedReader r = new BufferedReader(new InputStreamReader( p.getInputStream())); String firstLine = r.readLine(); if (firstLine.startsWith("This is jdb version")) { int exitCode = p.waitFor(); r.close(); if (exitCode != 0) { usage("can not correctly access jdb in the current environment."); } } } catch(InterruptedException ie) { usage("can not correctly access jdb in the current environment."); System.exit(-1); } catch(IOException e) { usage("can not correctly access jdb in the current environment."); System.exit(-1); } return new JavaDebugger(dbg, "jdb"); } /** * Choose a native debugger, check Blink can run this native debugger, and * create Blink's native debugger handler. If Blink can not correctly use the * native debugger, terminate the current debugging session. * * The OSTYPE environment variable controls which native debugger to use. */ private static NativeDebugger ensureNativeDebugger(Blink dbg) { String ostype = System.getenv("OSTYPE"); if (ostype == null || !ostype.equals("win32")) { // default is gdb. try { Process p = Runtime.getRuntime().exec("gdb -version"); BufferedReader r = new BufferedReader(new InputStreamReader( p.getInputStream())); String firstLine = r.readLine(); if (firstLine.startsWith("GNU gdb")) { int exitCode = p.waitFor(); r.close(); if (exitCode != 0) { usage("can not correctly access gdb in the current environment."); } } } catch(InterruptedException ie) { usage("can not correctly access gdb in the current environment."); } catch(IOException e) { usage("can not correctly access gdb in the current environment."); } return new NativeGDB(dbg, "gdb"); } else { //if OSTYPE is win32, use cdb. assert ostype.equals("win32"); try { Process p = Runtime.getRuntime().exec("cdb -version"); BufferedReader r = new BufferedReader(new InputStreamReader( p.getInputStream())); String versionLine = r.readLine(); if (versionLine.matches("cdb version \\d+\\.\\d+\\.\\d+\\.\\d+")) { int exitCode = p.waitFor(); r.close(); if (exitCode != 0) { usage("cdb returned non-zero exit code: " + exitCode + " when running cdb -version."); } } } catch(InterruptedException ie) { usage("can not correctly run cdb in the current environemnt."); } catch (IOException e) { usage("can not correctly run cdb in the current environment."); } return new NativeCDB(dbg, "cdb"); } } /** The application main class. */ private final String mainClass; /** The option string to be forwarded to the jvm. */ private final String[] jvmArguments; /** The option string to be forwarded to the jdb. */ private final String[] jdbArguments; /** The user command monitor. */ private final CommandLineInterface user; /** The application. */ final DebugeeJVM jvm; /** The jdb message monitor. */ final JavaDebugger jdb; /** The gdb message monitor. */ final NativeDebugger ndb; /** * The Blink event queue. Let the main thread be thread who initiated the * eventLoop() method, and this thread consumes events in the queue. There are * three event producers: user, jdb and gdb. */ private final LinkedBlockingQueue<Event> eventQueue = new LinkedBlockingQueue<Event>(); final EventLoop eventLoop; /** The current debugger control status. */ private DebugerControlStatus debugControl = DebugerControlStatus.NONE; /** * This is a flag to recode a fact that the jdb and gdb are initialized for * j2c and c2j debugger switching. */ private boolean gdbAttached = false; /** The break points manager. */ final BreakPointManager breakpointManager = new BreakPointManager(this); /** The Blink debugger options. */ final InternalOption options; /** * Construct the Blink debugger. * * @param mainClass The main class name. * @param jvmArguments The jvm command line options. * @param jdbArguments The jdb command line options. * @param gdbOptions The gdb command line options. * @param debugOption The Blink debugger options. */ private Blink(final String mainClass, final String[] jvmArguments, final String[] jdbArguments, final String gdbOptions, final InternalOption debugOptions) { this.mainClass = mainClass; this.jvmArguments = jvmArguments; this.jdbArguments = jdbArguments; this.options = debugOptions; this.jdb =ensureJavaDebugger(this); this.ndb = ensureNativeDebugger(this); this.user = new CommandLineInterface(this); this.jvm = new DebugeeJVM(this, "jvm"); this.eventLoop = new EventLoop(this); } /** * Start the Blink debugger and wait until the end of this Blink session. */ private void startSession() { // Launch Java debugger and get the listening address. jdb.startListening(jdbArguments); final String address = (String) EventLoop.subLoop(this, new EventLoop.ReplyHandler() { boolean dispatch(Event e) { if (e instanceof ListenAddressEvent && e.getSource() == Blink.this.jdb) { ListenAddressEvent listenEvent = (ListenAddressEvent) e; setResult(listenEvent.getAddress()); return true; } else { return false; } } }); // Launch JVM and attach the JVM to the JDB. jvm.beginDebugSession(jvmArguments, address); EventLoop.subLoop(this, new EventLoop.ReplyHandler() { boolean dispatch(Event e) { if (e instanceof InitializedEvent && e.getSource() == Blink.this.jdb) { setResult(new Boolean(true)); return true; } else { return false; } } }); // Advance the program execution to the main method. jdb.setBreakPoint(mainClass + ".main"); jdb.run(); EventLoop.subLoop(this, new EventLoop.ReplyHandler() { boolean dispatch(Event e) { if (e instanceof JavaBreakPointHitEvent) { JavaBreakPointHitEvent je = (JavaBreakPointHitEvent) e; if (je.getClassName().equals(mainClass) && je.getMethodName().equals("main")) { setResult(new Boolean(true)); return true; } else { assert false : "unknown Java breakpoint hit"; return false; } } else if (e instanceof RawTextMessageEvent) { return false; } else { assert false : "To be implemented for " + e.toString(); return false; } } }); changeDebugControlStatus(DebugerControlStatus.JDB); jdb.clearBreakPoint(mainClass + ".main"); initj(); // show welcome message. out("Blink a Java/C mixed language debugger.\n"); changeDebugControlStatus(DebugerControlStatus.JDB); // Now, enable the user command line processing. user.start(); showPrompt(); } /** * Present a terminal command prompt to the user. */ void showPrompt() { switch(debugControl) { case JDB: out("(bdb-j) "); break; case GDB: out("(bdb-c) "); break; case JDB_IN_GDB: out("(bdb-c2j) "); break; case GDB_IN_JDB: out("(bdb-j2c) "); break; } } /** Implement Blink "run" command. */ void run() { assert (getDebugControlStatus() == DebugerControlStatus.JDB); jdb.run(); changeDebugControlStatus(Blink.DebugerControlStatus.NONE); } /** * Prepare jdb and gdb for j2c and c2j debugger switching. Internally, this * will initilize the Blink debugger agent by running a number of jdb and gdb * commands. */ boolean initj() { if (gdbAttached) { err("the initj was run before."); return true; } assert debugControl == DebugerControlStatus.JDB; // try to initialize the DebugAgent jdb.setBreakPoint(BDA_AGENT_NAME + "." + BDA_JBP ); // Obtain the process ID of the debuggee by using the agent. int pid = Integer.parseInt(jdb.eval(BDA_GETPROCESSID)); // Attach gdb to the debugee using the process ID. ndb.attach(pid); gdbAttached = true; assert getDebugControlStatus() == DebugerControlStatus.JDB; return true; } /** * Implements the j2c macro command. This will activate the gdb assuming that * the jdb has control over the debuggee. */ void j2c() { if (!IsNativeDebuggerAttached()) { err("please run initj before j2c\n"); return; } jdb.j2c(); Event e = (Event) EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] { new EventFilter(ndb, J2CBreakPointHitEvent.class), new EventFilter(ndb, LanguageTransitionEvent.class), })); if (e instanceof LanguageTransitionEvent) { ndb.cont(); EventLoop.subLoop(this, new EventReplyHandler(new EventFilter[] { new EventFilter(ndb, J2CBreakPointHitEvent.class), })); } changeDebugControlStatus(DebugerControlStatus.GDB_IN_JDB); } /** * Implements the c2j macro command. This will activate the jdb assuming that * gdb has control over the debugee. */ void c2j() { if (!IsNativeDebuggerAttached()) { err("please run initj before c2j\n"); return; } ndb.callNative2Java(); EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] { new EventFilter(jdb, JavaBreakPointHitEvent.class), })); changeDebugControlStatus(DebugerControlStatus.JDB_IN_GDB); } /** * Implements the jret macro command. If the current debugger nesting is * jdb->gdb, then this will resume the gdb j2c break point, and the user input * redirection will be switched to the jdb. If the current debugger nesting is * gdb->jdb, this will resume the jdb c2j break point, and the user input * redirection will be switched to the gdb. */ void jret() { if (!IsNativeDebuggerAttached()) { err("please run initj before jret\n"); return; } switch (getDebugControlStatus()) { case NONE: case JDB: case GDB: err("jret is for jdb and gdb nesting."); break; case JDB_IN_GDB: jdb.cont(); EventLoop.subLoop(this, new EventReplyHandler(new EventFilter[] { new EventFilter(ndb, Native2JavaCompletionEvent.class), new EventFilter(ndb, LanguageTransitionEvent.class), })); changeDebugControlStatus(DebugerControlStatus.GDB); break; case GDB_IN_JDB: ndb.cont(); EventLoop.subLoop(this, new ReplyHandler() { boolean dispatch(Event e) { if (e instanceof Java2NativeCompletionEvent && e.getSource() == jdb) { setResult(new Boolean(true)); return true; } else if (e instanceof NativeBreakPointHitEvent && e.getSource() == ndb) { ndb.cont(); return false; } else { return false; } } }); changeDebugControlStatus(DebugerControlStatus.JDB); break; default: break; } } /** * Implement Blink "continue/cont" command. */ void cont() { assert debugControl != DebugerControlStatus.NONE; switch (getDebugControlStatus()) { case JDB: case GDB: if (breakpointManager.hasDeferredNativeBreakpoint()) { ensureJDBContext(); jdb.setLoadLibraryEvent(); } break; } switch (getDebugControlStatus()) { case JDB: jdb.cont(); changeDebugControlStatus(Blink.DebugerControlStatus.NONE); break; case GDB: ndb.cont(); changeDebugControlStatus(Blink.DebugerControlStatus.NONE); break; case JDB_IN_GDB: case GDB_IN_JDB: err("\"continue\" is not allowed in this nested mode:" + getDebugControlStatus() + "\n" + "use jret to return to the gdb.\n"); break; default: assert false : "not allowed"; break; } } /** * Perform inter-language source level stepping. */ Event step() { ensurePureContext(); Event e = null; SourceFileAndLine start = getCurrentSourceLevelLocation(); assert start != null; SymbolMapper.SourceFileAndLine now = null; do { switch(getDebugControlStatus()) { case JDB: e = stepj(); now = getCurrentSourceLevelLocation(); break; case GDB: e = stepc(); now = getCurrentSourceLevelLocation(); break; default: assert false; break; } } while(now == null || start.equals(now)); return e; } /** * Perform the inter-language source level stepping from Java context, and * return component debugger that finished this single stepping. * * @return The component event. */ private Event stepj() { assert getDebugControlStatus() == DebugerControlStatus.JDB; // try JDB step-into and expect pause at Java or native code. j2c(); ndb.setTransitionBreakPoint(LanguageTransitionEventType.J2C_CALL, 0); ndb.setTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN, 0); jret(); jdb.step(); boolean reachedSourceLine = false; Event e; do { changeDebugControlStatus(DebugerControlStatus.NONE); e = (Event)EventLoop.subLoop(this, new EventReplyHandler(new EventFilter[] { new EventFilter(jdb, JavaStepCompletionEvent.class), new EventFilter(jdb, JavaBreakPointHitEvent.class), new EventFilter(ndb, Java2NativeCallEvent.class), new EventFilter(ndb, Native2JavaReturnEvent.class) })); if (e.getSource() == jdb) { // stepping from Java remains inside the Java area. changeDebugControlStatus(DebugerControlStatus.JDB); j2c(); ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_CALL); ndb.clearTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN); jret(); reachedSourceLine = true; // assume all Java byte code has the source line here. } else { // the control will stay in the native code. assert e.getSource() == ndb; changeDebugControlStatus(DebugerControlStatus.GDB); if (ndb.getCurrentLocation() == null ) { changeDebugControlStatus(DebugerControlStatus.NONE); ndb.cont(); } else { ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_CALL); ndb.clearTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN); // flush the JDB's stepping status. ndb.callJavaDummy(); EventLoop.subLoop(this, new EventReplyHandler(new EventFilter[] { new EventFilter(jdb, JavaStepCompletionEvent.class) })); jdb.cont(); EventLoop.subLoop(this, new EventReplyHandler(new EventFilter[] { new EventFilter(ndb, DummyCallCompletionEvent.class) })); reachedSourceLine = true; } } } while(!reachedSourceLine); return e; } /** * Perform an inter-language source level stepping from native code, and return * component debugger event that finished this stepping. * * @return The component event. */ private Event stepc() { assert getDebugControlStatus() == DebugerControlStatus.GDB; //set breakpoints for escaping to the Java. ndb.setTransitionBreakPoint(LanguageTransitionEventType.C2J_CALL, 0); ndb.setTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN, 0); ndb.step(); Event e = (Event)EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] { new EventFilter(ndb, Native2JavaCallEvent.class), new EventFilter(ndb, Java2NativeReturnEvent.class), new EventFilter(ndb, NativeStepCompletionEvent.class), new EventFilter(ndb, NativeBreakPointHitEvent.class), })); ndb.clearTransitionBreakPoint(LanguageTransitionEventType.C2J_CALL); ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN); if (e instanceof NativeStepCompletionEvent || e instanceof NativeBreakPointHitEvent) { //done! } else if (e instanceof Native2JavaCallEvent) { Native2JavaCallEvent ne = (Native2JavaCallEvent)e; //reach the Java target method entry. String className= ne.getClassName(); int lineNumber = ne.getLineNumber(); c2j(); jdb.setBreakPoint(className, lineNumber); jret(); changeDebugControlStatus(DebugerControlStatus.NONE); ndb.cont(); EventLoop.subLoop( this, new EventReplyHandler( new EventFilter[] { new EventFilter(jdb, JavaBreakPointHitEvent.class), })); changeDebugControlStatus(DebugerControlStatus.JDB); jdb.clearBreakPoint(className, lineNumber); } else if (e instanceof Java2NativeReturnEvent) { Java2NativeReturnEvent ne = (Java2NativeReturnEvent)e; //reach the Java return site. String cname = ne.getJavaTarget(); int line = ne.getTargetLineNumber(); c2j(); jdb.setBreakPoint(cname, line); jret(); ensureGDBContext(); changeDebugControlStatus(DebugerControlStatus.NONE); ndb.cont(); EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] { new EventFilter(jdb, JavaBreakPointHitEvent.class), })); changeDebugControlStatus(DebugerControlStatus.JDB); jdb.clearBreakPoint(cname, line); } return e; } /** * Perform an inter-language source level stepping, and return the component * debugger event that terminated the single stepping. * * @return The component debugger event. */ Event next() { Event e = null; ensurePureContext(); SymbolMapper.SourceFileAndLine start = getCurrentSourceLevelLocation(); assert start != null; SymbolMapper.SourceFileAndLine now = null; do { switch(getDebugControlStatus()) { case JDB: e = nextj(); now = getCurrentSourceLevelLocation(); break; case GDB: e = nextc(); now = getCurrentSourceLevelLocation(); break; default: assert false; break; } } while(now == null || start.equals(now)); ensurePureContext(); assert e != null; return e; } /** * Perform a source-level step-over from the Java code, and return the * component debugger event that terminated this single stepping. At the end * of this operation, this debuggee is in either Java or native code. * * @return The component debugger event. */ private Event nextj() { assert debugControl == DebugerControlStatus.JDB; //set c2j_return breakpoint. j2c(); int languageTransitionCountOnStack = ndb.getLanguageTransitionCount(); ndb.setTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN, languageTransitionCountOnStack); jret(); //start a single step from Java. changeDebugControlStatus(DebugerControlStatus.NONE); jdb.next(); Event e = (Event)EventLoop.subLoop(this, new EventReplyHandler(new EventFilter[] { new EventFilter(jdb, JavaStepCompletionEvent.class), new EventFilter(jdb, JavaBreakPointHitEvent.class), new EventFilter(ndb, Native2JavaReturnEvent.class), new EventFilter(ndb, NativeBreakPointHitEvent.class), })); if (e.getSource() == jdb) { changeDebugControlStatus(DebugerControlStatus.JDB); j2c(); ndb.clearTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN); jret(); return e; } else { assert e.getSource() == ndb; changeDebugControlStatus(DebugerControlStatus.GDB); ndb.clearTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN); return e; } } /** * Perform a source-level step-over from the native code, and return the * component debugger event that terminated this single stepping. At the end * of this operation, this debuggee is in either Java or native code. * * @return The component debugger event. */ private Event nextc() { assert debugControl == DebugerControlStatus.GDB; //set language transition breakpoint. ensureGDBContext(); int languageTransitionCountOnStack = ndb.getLanguageTransitionCount(); ndb.setTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN, languageTransitionCountOnStack); //resume the execution with native stepping over. changeDebugControlStatus(DebugerControlStatus.NONE); ndb.next(); Event e = (Event)EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] { new EventFilter(ndb, NativeStepCompletionEvent.class), new EventFilter(ndb, NativeBreakPointHitEvent.class), new EventFilter(ndb, Java2NativeReturnEvent.class), new EventFilter(jdb, JavaBreakPointHitEvent.class), })); //handle the step completion cases if (e instanceof NativeStepCompletionEvent || e instanceof NativeBreakPointHitEvent) { changeDebugControlStatus(DebugerControlStatus.GDB); ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN); } else if (e instanceof Java2NativeReturnEvent) { changeDebugControlStatus(DebugerControlStatus.GDB); ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN); //continue until the program reaches the return site. ensureJDBContext(); Java2NativeReturnEvent event = (Java2NativeReturnEvent)e; String cname = event.getJavaTarget(); int line = event.getTargetLineNumber(); assert cname != null && line > 0 : "can not put byte code index break point with jdb."; jdb.setBreakPoint(cname, line); ensureGDBContext(); changeDebugControlStatus(DebugerControlStatus.NONE); ndb.cont(); boolean returnSiteReached = false; do { JavaBreakPointHitEvent jbpHitEvent = (JavaBreakPointHitEvent) EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] { new EventFilter(jdb, JavaBreakPointHitEvent.class), })); changeDebugControlStatus(DebugerControlStatus.JDB); returnSiteReached =jbpHitEvent.getClassName().equals(cname) && jbpHitEvent.getLineNumber() == line; } while (!returnSiteReached); jdb.clearBreakPoint(cname, line); changeDebugControlStatus(DebugerControlStatus.JDB); } else if (e instanceof JavaBreakPointHitEvent) { changeDebugControlStatus(DebugerControlStatus.JDB); j2c(); //this will flush the native single stepping. ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN); jret(); } else { assert false : " not reachable"; } return e; } /** Exit the current debugging session. * @ */ void exit() { ensurePureContext(); if (getDebugControlStatus() == DebugerControlStatus.NONE) { System.exit(0); assert false : "should not reach here"; return; } else if (getDebugControlStatus() == DebugerControlStatus.JDB) { // ensure gdb is detached. if (gdbAttached && !ndb.isDead()) { j2c(); ndb.detach(); debugControl = DebugerControlStatus.JDB; EventLoop.subLoop(this, new ConjunctiveReplyHandler( new J2CCompletionEventHandler(jdb), new DeathReplyHandler(ndb))); gdbAttached = false; } if (!jdb.isDead()) { jdb.exit(); EventLoop.subLoop(this, new ConjunctiveReplyHandler( new DeathReplyHandler(jvm), new DeathReplyHandler(jdb))); debugControl = DebugerControlStatus.NONE; } } else if (getDebugControlStatus() == DebugerControlStatus.GDB) { if (!ndb.isDead()) { ndb.quit(); EventLoop.subLoop(this, new ConjunctiveReplyHandler( new DeathReplyHandler(jvm), new DeathReplyHandler(jdb), new DeathReplyHandler(ndb))); } } else { assert false : "should not reach here"; return; } System.exit(0); } /** * Print a message to the console. * * @param msg The message to print. */ void out(String format, Object ... args) { String msg = format(format, args); user.out(msg); } /** * Print an message to the console. * * @param b The byte buffer. * @param off The offset. * @param len The length. */ void out(char[] b) { user.out(new String(b)); } /** * Print an error message to the console. * * @param msg The error message to print. */ void err(String msg) { user.err(msg); } /** * Getter method for the debuggerSwitchingInitialized. * * @return true if the debug context switching is ready, or false otherwise. */ boolean IsNativeDebuggerAttached() { return gdbAttached; } /** * Ensure the debug agent is ready. */ boolean ensureDebugAgent() { assert debugControl == DebugerControlStatus.JDB; if (IsNativeDebuggerAttached()) { return true; } return initj(); } /** * Ensure jdb is available. */ public boolean ensureJDBContext() { switch (debugControl) { case JDB: case JDB_IN_GDB: return true; case GDB: c2j(); return true; case GDB_IN_JDB: jret(); return true; default: assert false : "not allowed state"; return false; } } /** * try to ensure gdb is available. * * @return true if the gdb context. false otherwise. */ public boolean ensureGDBContext() { if (!IsNativeDebuggerAttached()) { return false; } switch (debugControl) { case JDB: j2c(); break; case JDB_IN_GDB: jret(); break; case GDB: case GDB_IN_JDB: break; default: assert false : "not allowed state"; break; } return true; } /** * Ensure a pure debug context. */ public final void ensurePureContext() { switch(getDebugControlStatus()) { case JDB: case GDB: break; case JDB_IN_GDB: case GDB_IN_JDB: jret(); break; case NONE: break; } } /** * Switch to the new debugger state. * * @param newStatus The requested new state. */ synchronized void changeDebugControlStatus(DebugerControlStatus newStatus) { DebugerControlStatus oldStatus = debugControl; if (oldStatus == newStatus) { return; } boolean success = false; switch (debugControl) { case NONE: success = newStatus == DebugerControlStatus.JDB || newStatus == DebugerControlStatus.GDB; break; case JDB: success = newStatus == DebugerControlStatus.NONE || newStatus == DebugerControlStatus.GDB_IN_JDB; break; case GDB: success = newStatus == DebugerControlStatus.NONE || newStatus == DebugerControlStatus.JDB_IN_GDB; break; case JDB_IN_GDB: success = newStatus == DebugerControlStatus.GDB; break; case GDB_IN_JDB: success = newStatus == DebugerControlStatus.JDB; break; } debugControl = newStatus; if (!success) { err("warning: not an expected transition in the following context\n" + oldStatus + "->" + newStatus); Thread.dumpStack(); } } /** * @return The debugger control status. */ synchronized DebugerControlStatus getDebugControlStatus() { return debugControl; } /** * @return The current language context */ synchronized String getCurrentLanguageContext() { switch (debugControl) { case JDB_IN_GDB: case JDB: return "Java"; case GDB_IN_JDB: case GDB: return "C"; default: return "None"; } } /** * Get current location where the debugee suspened. * @return The location. */ SourceFileAndLine getCurrentSourceLevelLocation() { ensurePureContext(); SourceFileAndLine location; switch(debugControl) { case JDB: location = jdb.getCurrentLocation(); break; case GDB: location = ndb.getCurrentLocation(); break; default: assert false: "not reachable"; location = null; break; } return location; } String getCurrentSourceLine() { ensurePureContext(); String line; switch(debugControl) { case JDB: List<JavaCallFrame> frames = jdb.getFrames(); assert frames.size() > 0; JavaCallFrame top = frames.get(0); line = jdb.getSourceLine(top); break; case GDB: SourceFileAndLine loc = getCurrentSourceLevelLocation(); line = ndb.getSourceLines(loc.getSourceFile(), loc.getSourceLine(), 1); break; default: assert false: "not reachable"; line = null; break; } return line; } /** * Enqueue an event. * * @param e The event. */ void enqueEvent(Event e) { if (!eventQueue.add(e)) { err("can not successfully insert event: " + e + " from " + Thread.currentThread() + "\n"); err("Now, I'm discarding that event\n"); } } /** * Dequeue an event. * * @return The event. */ Event dequeEvent() { Event event = null; try { event = eventQueue.take(); } catch (InterruptedException e) { if (event == null) { err("could not get non-null event from queue\n"); return null; } } return event; } /** * Getter method for mainClass. * * @return The mainClass. */ public String getMainClass() { return mainClass; } }