package xtc.lang.blink; import java.io.IOException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import xtc.lang.blink.Blink.DebugerControlStatus; import xtc.lang.blink.CallStack.LocalVariable; import xtc.lang.blink.CallStack.JavaCallFrame; import xtc.lang.blink.Event.Java2NativeCompletionEvent; import xtc.lang.blink.Event.JavaBreakPointHitEvent; import xtc.lang.blink.Event.JavaExceptionEvent; import xtc.lang.blink.Event.JavaLoadLibraryEvent; import xtc.lang.blink.Event.JavaStepCompletionEvent; import xtc.lang.blink.Event.RawTextMessageEvent; import xtc.lang.blink.EventUtil.RegExpReplyHandler; import xtc.lang.blink.SymbolMapper.SourceFileAndLine; import xtc.lang.blink.agent.AgentJavaDeclaration; /** JDB driver for Blink debugger. */ public class JavaDebugger extends StdIOProcess implements AgentJavaDeclaration { /** JDB's macro event. */ public static class ListenAddressEvent extends Event { /** JVM JDWP listen address. */ private final String address; /** Constructor. */ ListenAddressEvent(JavaDebugger jvm, String address) { super(jvm); this.address = address; } /** Getter methods. */ public String getAddress() { return address;} public String getName() {return "JDBListen:" + address;} } /** JDB's macro event. */ public static class InitializedEvent extends Event { /** Constructor. */ InitializedEvent(JavaDebugger jdb) { super(jdb); } /** Getter methods. */ public String getName() {return "JDBInitialized:";} } /** The pattern cache. */ private static final HashMap<String,Pattern> patternCache = new HashMap<String,Pattern>(); private static final Pattern p(String patternString) { Pattern pattern = patternCache.get(patternString); if (pattern == null) { pattern = Pattern.compile(patternString); patternCache.put(patternString, pattern); } return pattern; } /** * A state when JDB is waiting for the completion of the J2C debugger * transition. */ private boolean j2c_pending = false; /** The internal message buffer to parse the JDB's output. */ private final StringBuffer sbStdout = new StringBuffer(); /** Whether or not the JDB' listen address event was fired. */ private boolean jdbListenAddressEventFired = false; /** Whether or not the JDB's initialization event was fired. */ private boolean jdbInitializedEventFired = false; /** * @param dbg The Blink debugger. * @param name The user friendly name. */ public JavaDebugger(Blink dbg, String name) { super(dbg, name); } /** * Start a JDB process. * @param args The JDB extra argument. */ public void startListening(final String args) throws IOException { final String jdbCommand = "jdb -listenany " + args; if (dbg.options.getVerboseLevel() >= 1) { dbg.out("executing: " + jdbCommand + "\n"); } begin(jdbCommand.split("\\s+"));; } public boolean initAgent() throws IOException { //install internal breakpoint. raeJDB("stop in " + BDA_AGENT_NAME + "." + BDA_JBP + "\n"); return true; } public int getJVMProcessID() throws IOException { String pidString = evalAgentMethod(BDA_GETPROCESSID); int pid= Integer.parseInt(pidString); return pid; } public void j2c() throws IOException { sendMessage("eval " + BDA_AGENT_NAME + "." + BDA_J2C + "()\n"); j2c_pending = true; } public void run() throws IOException { sendMessage("run\n"); } /** Let the Debugee continue.*/ public void cont() throws IOException { sendMessage("cont\n"); } public void step() throws IOException { sendMessage("step\n"); } public void next() throws IOException { sendMessage("next\n"); } public void stepi() throws IOException { raeJDB("stepi\n"); } public void exit() throws IOException { sendMessage("exit\n"); } public void setLoadLibraryEvent() throws IOException { raeJDB("stop in java.lang.System.loadLibrary\n"); } public void resetLoadLibraryEvent() throws IOException { raeJDB("clear java.lang.System.loadLibrary\n"); } public void prepareLoadLibrary() throws IOException { raeJDB("step up\n"); } public void setBreakPoint(String className, int line) throws IOException { raeJDB("stop at " + className + ":" + line + "\n"); } public void clearBreakPoint(String classNameAndMethod, int line) throws IOException { raeJDB("clear " + classNameAndMethod + ":" + line + "\n"); } public void setBreakPoint(String classNameAndMethod) throws IOException { raeJDB("stop in " + classNameAndMethod + "\n"); } public void clearBreakPoint(String classNameAndMethod) throws IOException { raeJDB("clear " + classNameAndMethod + "\n"); } /** * Let the Blink debugger to talk to the jdb and obtain Java stack frame list. * Then, parse the output messages and constuct a list of Java stack frames. * * @return The list of java stack frames. */ public List<JavaCallFrame> getFrames() throws IOException { // run the jdb command. String output = raeJDB("where\n"); // parse the output from jdb. LinkedList<JavaCallFrame> javaFrames = new LinkedList<JavaCallFrame>(); Pattern frameLinePattern = Pattern.compile("^\\s+" + "\\[([0-9]+)\\]" + "\\s+" // frame id -- > g1 + "((\\S+)\\.([^\\.]+))" + "\\s+" // class(g3) and method(g4) + "(" // g5 + "\\(([^:]+):([0-9]+)\\)" // sourceName(g6) and Line(g7) + "|" // or + "(\\(native method\\))" // "(native frame)"(g8) +")" + "$" ); for (final String line : output.split("\n")) { Matcher m = frameLinePattern.matcher(line); if (!m.find()) { dbg.err("could not recognize jdb output: " + line + "\n"); continue; } int id = Integer.valueOf(m.group(1)); String className = m.group(3); String methodName = m.group(4); String srcFile= m.group(6); int lineno = m.group(7) == null ? -1: Integer.parseInt(m.group(7)); boolean isTransition = m.group(8) != null; if (srcFile != null && srcFile.equals(BDA_AGENT_SOURCE_FILE)) { continue; //skip frames related to the c2j transition. } if (className.equals("sun.reflect.NativeMethodAccessorImpl") && methodName.equals("invoke0")) { // Treat the reflection frame as if a pure Java frame because the // reflection bypasses the c2j_proxy in Hotspot and J9. isTransition = false; } JavaCallFrame frame = new JavaCallFrame(id, srcFile, lineno, isTransition, className, methodName); javaFrames.addLast(frame); } return javaFrames; } /** Get current location. */ public SourceFileAndLine getCurrentLocation() throws IOException { List<JavaCallFrame> list = getFrames(); SourceFileAndLine loc = null; if (list.size() > 0 ) { JavaCallFrame top = list.get(0); String sourceFile = top.getSourceFile(); int line = top.getLineNumber(); if (sourceFile != null && line >= 0) { loc = new SourceFileAndLine(sourceFile, line); } } else { assert false: "empty Java stack frames"; } assert loc != null: "Java frames without source lines information"; return loc; } /** * The the current source line for a frame. * @param jframe The frame. * @return The source line. */ String getSourceLine(JavaCallFrame jframe) throws IOException { selectFrame(jframe); String lines = raeJDB("list\n"); String line = ""; StringTokenizer t = new StringTokenizer(lines, "\n"); while(t.hasMoreTokens()) { String l = t.nextToken(); if (l.matches("\\d+ =>.*")) { line = l; } else if (l.matches("Source file not found: .+")) { line = l; } } return line; } /** * Select a java frame in the jdb context. * * @param dbg The debugger. * @param jframe The Java frame. */ private void selectFrame(JavaCallFrame jframe) throws IOException { DebugerControlStatus dbgControl = dbg.getDebugControlStatus(); assert dbgControl == DebugerControlStatus.JDB || dbgControl == DebugerControlStatus.JDB_IN_GDB; int target = jframe.getJdbIdentifier(); String posBefore = raeJDB( "where\n", " \\[(\\d+)\\] .+\\n" + "(?:.*\\n)*\\S+\\[\\d+\\] " ).group(1); int before = Integer.parseInt(posBefore); int diff = target - before; if (diff > 0) { // up toward bottom of stack. raeJDB("up " + diff + "\n"); } else if (diff < 0) { // down toward bottom of stack. int nWalks = - (diff); raeJDB("down " + nWalks + "\n"); } String posAfter = raeJDB( "where\n", "\\s \\[(\\d+)\\] .+\\n" + "(?:.*\\n)*" + "\\S+\\[\\d+\\] " ).group(1); int after = Integer.parseInt(posAfter); assert after == target; } public String list(JavaCallFrame f) throws IOException { assert f != null; selectFrame(f); return raeJDB("list\n"); } public List <LocalVariable> getLocals(JavaCallFrame f) throws IOException { LinkedList<LocalVariable> list = new LinkedList<LocalVariable>(); assert f != null; selectFrame(f); String locals = raeJDB("locals\n"); Pattern p = Pattern.compile("^(.+) = (.+)$"); for(final String line : locals.split("\n")) { Matcher m = p.matcher(line); if (m.matches()) { String name = m.group(1); String value = m.group(2); list.add(new LocalVariable(name, value)); } else if (line.equals("Method arguments:")) { //skip } else if (line.equals("Local variables:")) { //skip } else if (line.equals("")) { } else { dbg.err("can not recognize this JDB output: " + line + "\n"); } } return list; } public String newConvenienceVariable(JavaCallFrame f, String jexpr) throws IOException { if (f != null) { selectFrame(f); } String actualExpr = BDA_AGENT_VARIABLE_NAME + "." + BDA_SETVJFROMJAVAEXPR + "(" + jexpr + ")"; String vjid = raeJDB( "eval " + actualExpr + "\n", ".+ = (.+)\\n" + "(?:.*\\n)*" + "\\S+\\[\\d+\\] " ).group(1); return vjid; } public String getConvenienceVariableJNIType(String vjid) throws IOException { return evalAgentVariableMethod(BDA_GETVJJNITYPE, vjid); } public String getConvenienceVariableJavaType(String vjid) throws IOException { return evalAgentVariableMethod(BDA_GETJAVATYPE, vjid); } public String getConvenienceVariableRValueExpression(String vjid) throws IOException { return evalAgentVariableMethod(BDA_getVJExpr, vjid); } public String print(JavaCallFrame f, String expr) throws IOException { if (f != null) { selectFrame(f); } String rst = raeJDB( "print " + expr + "\n", "\\S+ = (\\S+)\\n" + "(?:.*\\n)*\\S+\\[\\d+\\] " ).group(1); return rst; } public String runCommand(String cmd) throws IOException { return raeJDB(cmd+ "\n"); } public void resetConvenienceVariables() throws IOException { raeJDB("eval " + BDA_AGENT_VARIABLE_NAME + "." + BDA_CLEANTEMPVARS + "()\n"); } public void dispatch(Event e) {} /** * Execute agent's method, and return the returned value. * * @param mname The method name. */ private String evalAgentMethod(String mname) throws IOException { return raeJDB("eval " + BDA_AGENT_NAME + "." + mname + "()\n", BDA_AGENT_NAME + "." + mname + "\\(\\) = (.+)\\n" + "(?:.*\\n)*\\S+\\[\\d+\\] " ).group(1); } private String evalAgentVariableMethod(String mname, String vjid) throws IOException { String actualExpr = BDA_AGENT_VARIABLE_NAME + "." + mname + "(" + vjid + ")"; String result = raeJDB( "eval " + actualExpr + "\n", ".+ = (.+)\\n" + "(?:.*\\n)*" + "\\S+\\[\\d+\\] " ).group(1); if (result.matches("\".+\"")) { result = result.substring(1, result.length()-1); } return result; } /** * Send a message to the gdb process, and wait until the gdb sends back an * expected message. Here, the expected message is a regular expression, and * its corresponding matcher obejct will be returned. * * @param msg The message to send. * @param expect The message to be expected from the gdb. * @return The matched string for the regular expression. */ private Matcher raeJDB(final String msg, String expect) throws IOException { sendMessage(msg); return (Matcher)EventLoop.subLoop(dbg,new RegExpReplyHandler(this, expect)); } /** * Issue a gdb command, and get text message fro m the gdb. * * @param cmd The command. * @return The result messgage. */ private String raeJDB(final String cmd) throws IOException { return raeJDB(cmd, "((?:.+\\n)*)\\S+\\[\\d+\\] ").group(1); } /** * Internally process the raw message to generate macro event. * * @param e The raw text message event. */ protected void processMessageEvent(RawTextMessageEvent e) { sbStdout.append(new String(e.getMessage())); // try to find event. if (!jdbListenAddressEventFired) { Matcher m = p("Listening at address: (\\S+)\\n").matcher(sbStdout); if (m.find()) { String reminder = sbStdout.substring(m.end()); String address = m.group(1); sbStdout.setLength(0); sbStdout.append(reminder); ListenAddressEvent listenEvent = new ListenAddressEvent(this, address); dbg.enqueEvent(listenEvent); jdbListenAddressEventFired = true; } } else if (!jdbInitializedEventFired) { Matcher m = p("Initializing jdb ...\\n" + "(.*\\n)*" + ".+\\[1\\] ").matcher(sbStdout); if (m.find()) { String reminder = sbStdout.substring(m.end()); sbStdout.setLength(0); sbStdout.append(reminder); InitializedEvent initEvent = new InitializedEvent(this); dbg.enqueEvent(initEvent); jdbInitializedEventFired = true; } } Matcher mBreakpoint = p("Breakpoint hit: .*" + ".thread=(.+)., (.+)\\.([^\\.]+)\\(\\), line=([0-9,]+) bci=([0-9]+)\\n" + "((?:.*\\n)*)" + ".+\\[\\d+\\] ").matcher(sbStdout); if (mBreakpoint.find()) { int end = mBreakpoint.end(); String reminder = sbStdout.substring(end); String threadName = mBreakpoint.group(1); String cname = mBreakpoint.group(2); String mname = mBreakpoint.group(3); int line = Integer.valueOf(mBreakpoint.group(4).replace(",", "")); int bcindex = Integer.valueOf(mBreakpoint.group(5)); String msg = mBreakpoint.group(6); sbStdout.setLength(0); sbStdout.append(reminder); Event bpe; if (cname.equals("java.lang.System") && mname.equals("loadLibrary")) { bpe = new JavaLoadLibraryEvent(this, threadName, cname, line, mname, bcindex, msg); } else { bpe = new JavaBreakPointHitEvent(this, threadName, cname, line, mname, bcindex, msg); } dbg.enqueEvent(bpe); return; } Matcher mException = p("Exception occurred: (.+) \\(uncaught\\)" + ".thread=(.+)., (.+)\\.([^\\.]+)\\(\\), line=([0-9,]+) bci=([0-9]+)\\n" + "((?:.*\\n)*)" + ".+\\[\\d+\\] ").matcher(sbStdout); if (mException.find()) { int end = mException.end(); String reminder = sbStdout.substring(end); String exceptionClass = mException.group(1); String thread = mException.group(2); String cname = mException.group(3); String mname = mException.group(4); int lineNumber = Integer.valueOf(mException.group(5).replace(",", "")); int bcindex = Integer.valueOf(mException.group(6)); String msg = mException.group(7); JavaExceptionEvent bpe = new JavaExceptionEvent(this, thread, cname, lineNumber, mname, bcindex, msg, exceptionClass); dbg.enqueEvent(bpe); sbStdout.setLength(0); sbStdout.append(reminder); return; } Matcher mstep = p("Step completed: " + ".*" + ".thread=(.+)., (.+)\\.([^\\.]+)\\(\\), line=([0-9,]+) bci=([0-9]+)\\n" + "((?:.*\\n)*)" + ".+\\[\\d+\\] ").matcher(sbStdout); if (mstep.find()) { int end = mstep.end(); String reminder = sbStdout.substring(end); String threadName = mstep.group(1); String cname = mstep.group(2); String mname = mstep.group(3); int line = Integer.valueOf(mstep.group(4).replace(",", "")); int bcindex = Integer.valueOf(mstep.group(5)); String msg = mstep.group(6); JavaStepCompletionEvent jse = new JavaStepCompletionEvent(this, threadName, cname, line, mname, bcindex, msg); dbg.enqueEvent(jse); sbStdout.setLength(0); sbStdout.append(reminder); } if (j2c_pending) { Matcher m = p(BDA_AGENT_NAME + "." + BDA_J2C +"\\(\\) = <void value>\\n" + "(?:.+)\\[\\d+\\] ").matcher(sbStdout); if (m.find()) { int end = m.end(); String reminder = sbStdout.substring(end); Java2NativeCompletionEvent je = new Java2NativeCompletionEvent(this); j2c_pending = false; dbg.enqueEvent(je); sbStdout.setLength(0); sbStdout.append(reminder); } } } }