0001: package xtc.lang.jeannie;
0002:
0003: import java.io.BufferedOutputStream;
0004: import java.io.BufferedReader;
0005: import java.io.BufferedWriter;
0006: import java.io.IOException;
0007: import java.io.InputStream;
0008: import java.io.InputStreamReader;
0009: import java.io.OutputStream;
0010: import java.io.OutputStreamWriter;
0011: import java.util.regex.Matcher;
0012: import java.util.regex.Pattern;
0013:
0014: import xtc.tree.GNode;
0015:
0016: /**
0017: * The Blink terminal debugger. This debugger internally uses jdb and gdb to
0018: * support the Jeannie source level debugging.
0019: *
0020: * @author Byeongcheol (BK) Lee
0021: */
0022: public class Debugger {
0023:
0024: /**
0025: * The debugger control status.
0026: */
0027: public enum DebugerControlStatus {
0028: NONE, JDB, GDB, JDB_IN_GDB, GDB_IN_JDB,
0029: }
0030:
0031: /**
0032: * The current state of jdb process.
0033: */
0034: private enum ProcessState {
0035: NOT_CREATED, LIVE, DEAD,
0036: }
0037:
0038: /**
0039: * Prints a usage message to the user, and terminate the Blink debugger with
0040: * an error code(-1).
0041: *
0042: * @param msg A message to the user to show what is wrong in the command line.
0043: */
0044: private static void usage(String msg) {
0045: StringBuffer buf = new StringBuffer();
0046: if (msg != null && msg.length() > 0) {
0047: buf.append(msg).append("\n\n");
0048: }
0049: buf
0050: .append("Usage: xtc.lang.jennie.Debugger [options] CLASS [arguments]\n");
0051: buf.append("Blink options include:\n");
0052: buf.append("\t-help\n");
0053: buf.append('\n');
0054:
0055: buf.append("options forwarded to jdb include:\n");
0056: buf.append("\t-sourcepath <directories separated by \":\">\n");
0057: buf.append("\t-attach <address>\n");
0058: buf.append("\t-listen <address>\n");
0059: buf.append("\t-listenany\n");
0060: buf.append("\t-launch\n");
0061: buf.append("\t-dbgtrace [flags]\n");
0062: buf.append("\t-tclient\n");
0063: buf.append("\t-tserver\n");
0064: buf.append('\n');
0065:
0066: buf
0067: .append("options forwarded to debuggee through jdb include:\n");
0068: buf.append("\t-v -verbose[:class|gc|jni]\n");
0069: buf.append("\t-D<name>=<value> system property\n");
0070: buf.append("\t-classpath <directories separated by \":\">\n");
0071: buf.append("\t-X<option>\n");
0072:
0073: System.out.println(buf.toString());
0074: System.exit(-1);
0075: }
0076:
0077: /**
0078: * Print the help message for the Blink command.
0079: *
0080: */
0081: void printCommandHelp() {
0082: final StringBuilder sb = new StringBuilder();
0083: sb.append("\n");
0084: sb.append("help print help\n");
0085: sb.append("exit exit the Blink debugger\n");
0086: sb.append("run start the program run\n");
0087: sb.append("\n");
0088: sb
0089: .append("break [file:line] add a break point e.g.) break Main.jni:9\n");
0090: sb
0091: .append("stop at <classid>:<line> add a break point e.g.) stop at Main:15\n");
0092: sb.append("watch <expr> add a watch point \n");
0093: sb
0094: .append("watch [access/all] <class id>.<fieldname> add a watch point\n");
0095: sb.append("info break list break points\n");
0096: sb.append("info watch list watch points\n");
0097: sb
0098: .append("delete [n] delete a break/watch point with its id [n].\n");
0099: sb.append("\n");
0100: sb.append("where dump stack trace\n");
0101: sb.append("up n select n frames up\n");
0102: sb.append("down n select n frames down\n");
0103: sb
0104: .append("locals print local variables in selected frame\n");
0105: sb.append("\n");
0106: sb.append("continue coninue running.\n");
0107: sb
0108: .append("step execute until another line reached\n");
0109: sb
0110: .append("next execute the next line, including function calls\n");
0111: sb.append("\n");
0112: sb
0113: .append("print <jexpr> print Jeannie expression\n");
0114: sb.append("list print source code.\n");
0115: out(sb.toString());
0116: }
0117:
0118: /**
0119: * The main method for the Blink debugger.
0120: *
0121: * @param args The command line arguments.
0122: */
0123: public static void main(String[] args) {
0124: StringBuffer sbJDBOptions = new StringBuffer();
0125: StringBuffer sbGDBOPtions = new StringBuffer();
0126: String mainClass = null;
0127: StringBuffer sbMainOptions = new StringBuffer();
0128:
0129: // parse arguments
0130: for (int i = 0; i < args.length; i++) {
0131: String arg = args[i];
0132: // [options] CLASS
0133: if (mainClass == null) {
0134: if (arg.equals("-help")) {
0135: usage("");
0136: } else if (arg.equals("-sourcepath")) {
0137: if ((i + 1) >= args.length)
0138: usage("Please, specify path after -sourcepath.");
0139: String argPath = args[++i];
0140: sbJDBOptions.append(' ').append(arg).append(' ')
0141: .append(argPath);
0142: } else if (arg.equals("-attach")) {
0143: if ((i + 1) >= args.length)
0144: usage("Please, specify <address> after -attach.");
0145: String argPath = args[++i];
0146: sbJDBOptions.append(' ').append(arg).append(' ')
0147: .append(argPath);
0148: } else if (arg.equals("-dbgtrace")) {
0149: sbJDBOptions.append(' ').append(arg);
0150: } else if (arg.equals("-classpath")) {
0151: if ((i + 1) >= args.length)
0152: usage("Please, specify path after -classpath.");
0153: String argPath = args[++i];
0154: sbJDBOptions.append(' ').append(arg).append(' ')
0155: .append(argPath);
0156: } else if (arg.matches("-D[^=]+=.*")) {
0157: sbJDBOptions.append(' ').append(arg);
0158: } else {
0159: mainClass = arg;
0160: }
0161: } else {
0162: // [arguments]
0163: sbMainOptions.append(' ').append(args);
0164: }
0165: }
0166:
0167: // check arguments
0168: if (mainClass == null) {
0169: usage("Please, specify the main CLASS name.");
0170: }
0171:
0172: // Now, run the Blink debugger.
0173: StringBuffer jdbArguments = new StringBuffer();
0174: jdbArguments.append(sbJDBOptions);
0175: jdbArguments.append(' ').append(mainClass);
0176: jdbArguments.append(' ').append(sbMainOptions);
0177: Debugger jeannieDebugger = new Debugger(
0178: jdbArguments.toString(), sbGDBOPtions.toString());
0179: try {
0180: jeannieDebugger.startDebugging();
0181: jeannieDebugger.runUserCommandLoop();
0182: } catch (Exception e) {
0183: e.printStackTrace();
0184: } finally {
0185: jeannieDebugger.ensureResourceRelease();
0186: }
0187:
0188: }
0189:
0190: /**
0191: * The option string to be forwarded to the jdb.
0192: */
0193: private final String jdbArguments;
0194:
0195: /**
0196: * The jdb slave process.
0197: */
0198: private SlaveProcessHandler jdbHandler;
0199:
0200: /**
0201: * The option string to be forwarded to the gdb.
0202: */
0203: private final String gdbOptions;
0204:
0205: /**
0206: * The gdb slave process.
0207: */
0208: private SlaveProcessHandler gdbHandler;
0209:
0210: /**
0211: * This flag is set to be true at the beginning. If any slave process is dead,
0212: * this flag is set to be false, indicating that the current debugging session
0213: * should terminate.
0214: */
0215: boolean allSlaveProcessLive = true;
0216:
0217: /**
0218: * The current debugger control status.
0219: */
0220: private DebugerControlStatus debugerControlStatus = DebugerControlStatus.NONE;
0221:
0222: /**
0223: * The user terminal input stream.
0224: */
0225: private final BufferedReader userInput;
0226:
0227: /**
0228: * The user terminal output stream.
0229: */
0230: private final BufferedOutputStream userOutput;
0231:
0232: /**
0233: * The user terminal error stream.
0234: */
0235: private final BufferedOutputStream userError;
0236:
0237: /**
0238: * This is a flag to recode a fact that the jdb and gdb are initialized for
0239: * j2c and c2j debugger switching.
0240: */
0241: private boolean debuggerSwitchingInitialized = false;
0242:
0243: /**
0244: * The actual implementation of the blink macro command.
0245: */
0246: final DebuggerCommand debuggerCommand;
0247:
0248: /**
0249: * The Blink command parser.
0250: */
0251: final DebuggerInterpreter interpreter;
0252:
0253: /**
0254: * @param jdbArguments The jdb command line options.
0255: * @param gdbOptions The gdb command line options.
0256: */
0257: private Debugger(final String jdbArguments, final String gdbOptions) {
0258: this .jdbArguments = jdbArguments;
0259: this .gdbOptions = gdbOptions;
0260: //prepares terminal I/O members
0261: this .userInput = new BufferedReader(new InputStreamReader(
0262: System.in));
0263: this .userOutput = new BufferedOutputStream(System.out);
0264: this .userError = new BufferedOutputStream(System.err);
0265:
0266: this .debuggerCommand = new DebuggerCommand(this );
0267: this .interpreter = new DebuggerInterpreter(this );
0268: }
0269:
0270: /**
0271: * Start debugging processes with jdb.
0272: */
0273: private void startDebugging() throws IOException {
0274: //jdb - attach the debugee
0275: String jdbCommand = "jdb " + jdbArguments;
0276: out("lanuching jdb : " + jdbCommand);
0277: jdbHandler = new SlaveProcessHandler("jdb", jdbCommand);
0278: jdbHandler.requestExpect("Initializing jdb ...");
0279: jdbHandler.start();
0280: jdbHandler.waitForExpect();
0281: jdbHandler.runAndWait("stop in DebugHelper.c2j\n",
0282: "Deferring breakpoint DebugHelper.c2j");
0283: jdbHandler.runAndWait("stop in java.lang.System.loadLibrary\n",
0284: "Deferring breakpoint java.lang.System.loadLibrary.");
0285: out("jdb is initialized.");
0286: }
0287:
0288: /**
0289: * Run the user command loop. Ask the user a command, and redirect the command
0290: * to the Blink macro command executer. This command loop may return if any
0291: * slave processes such as jdb, gdb and debuggee terminates or if the user
0292: * asks the termination with "exit" command.
0293: */
0294: private void runUserCommandLoop() {
0295: try {
0296: changeDebugStatus(DebugerControlStatus.JDB);
0297: sendUserMessage("\n");
0298:
0299: // The user command loop.
0300: for (String line = userInput.readLine(); line != null
0301: && allSlaveProcessLive; line = userInput.readLine()) {
0302: final String language;
0303: switch (debugerControlStatus) {
0304: case JDB_IN_GDB:
0305: case JDB:
0306: language = "Java";
0307: break;
0308: case GDB_IN_JDB:
0309: case GDB:
0310: language = "C";
0311: break;
0312: default:
0313: language = null;
0314: assert false;
0315: break;
0316: }
0317: final Object astOrMsg = Utilities
0318: .debuggerParseAndAnalyze(language, line,
0319: debuggerCommand.dbgExpr);
0320: if (astOrMsg instanceof GNode) {
0321: final GNode ast = (GNode) astOrMsg;
0322: if (ast.hasName("ExitCommand"))
0323: break;
0324: interpreter.dispatch(ast);
0325: } else {
0326: err((String) astOrMsg);
0327: }
0328: }
0329: } catch (IOException e) {
0330: System.err.println("error in the system input stream");
0331: e.printStackTrace();
0332: }
0333: }
0334:
0335: /**
0336: * Implement Blink "run" command.
0337: */
0338: void run() {
0339: try {
0340: assert (getControlStatus() == DebugerControlStatus.JDB);
0341: runAndWaitJDB("run\n", "run");
0342: changeDebugStatus(Debugger.DebugerControlStatus.NONE);
0343: } catch (IOException e) {
0344: err("could not successfully run the application");
0345: }
0346: }
0347:
0348: /**
0349: * Implements the j2c macro command. This will activate the gdb assuming that
0350: * the jdb has control over the debuggee.
0351: */
0352: void j2n() {
0353: if (!IsDebuggerSwitchingInitialized()) {
0354: out("please run initj before j2c");
0355: return;
0356: }
0357: out("running j2c and switching gdb user interaction...");
0358: try {
0359: runJDBAndWaitGDB("eval DebugHelper.j2c()\n",
0360: "Program received signal SIGTRAP, Trace/breakpoint trap.");
0361: changeDebugStatus(DebugerControlStatus.GDB_IN_JDB);
0362: } catch (IOException e) {
0363: err("failed in executing j2c\n");
0364: e.printStackTrace();
0365: }
0366: }
0367:
0368: /**
0369: * Implements c2j macro command. This will activate the jdb assuming that gdb
0370: * has control over the debugee.
0371: */
0372: void n2j() {
0373: if (!IsDebuggerSwitchingInitialized()) {
0374: out("please run initj before c2j");
0375: return;
0376: }
0377: out("running c2j and switching jdb user interaction...");
0378: try {
0379: runGDBAndWaitJDB("call DebugHelper_c2j()\n",
0380: "Breakpoint hit:");
0381: changeDebugStatus(DebugerControlStatus.JDB_IN_GDB);
0382: } catch (IOException e) {
0383: err("failed in executing c2j\n");
0384: e.printStackTrace();
0385: }
0386: }
0387:
0388: /**
0389: * Implements jret macro command. If the current debugger nesting is jdb->gdb,
0390: * then this will resume the gdb break point, and the user input redirection
0391: * will be switched to the jdb. If the current debugger nesting is gdb->jdb,
0392: * this will resume the jdb break point, and the user input redirection will
0393: * be switched to the gdb.
0394: */
0395: void jret() {
0396: if (!IsDebuggerSwitchingInitialized()) {
0397: out("please run initj before jret");
0398: return;
0399: }
0400: switch (getControlStatus()) {
0401: case NONE:
0402: case JDB:
0403: case GDB:
0404: out("jret is for jdb and gdb nesting.");
0405: break;
0406: case JDB_IN_GDB:
0407: out("return to gdb");
0408: try {
0409: runJDBAndWaitGDB("cont\n", "= 1");
0410: changeDebugStatus(DebugerControlStatus.GDB);
0411: } catch (IOException e) {
0412: err("failed in resuming jdb control nested in the gdb");
0413: }
0414: break;
0415: case GDB_IN_JDB:
0416: out("returning to jdb");
0417: try {
0418: runGDBAndWaitJDB("continue\n",
0419: "DebugHelper.j2c\\(\\) = <void value>");
0420: changeDebugStatus(DebugerControlStatus.JDB);
0421: } catch (IOException e) {
0422: err("failed in resuming gdb control nested in the jdb");
0423: }
0424: break;
0425: default:
0426: break;
0427: }
0428: }
0429:
0430: /**
0431: * Implement Blink "continue/cont" command.
0432: *
0433: */
0434: void cont() {
0435: switch (getControlStatus()) {
0436: case JDB:
0437: try {
0438: sendUserMessage("cont\n");
0439: changeDebugStatus(Debugger.DebugerControlStatus.NONE);
0440: } catch (IOException e) {
0441: err("failed in executing the continue");
0442: }
0443: break;
0444: case GDB:
0445: try {
0446: sendUserMessage("continue\n");
0447: changeDebugStatus(Debugger.DebugerControlStatus.NONE);
0448: } catch (IOException e) {
0449: err("failed in executing the continue");
0450: }
0451: break;
0452: case JDB_IN_GDB:
0453: out("\"continue\" is not allowed in jdb nested in the gdb.");
0454: out("use jret to return to the gdb");
0455: break;
0456: case GDB_IN_JDB:
0457: out("\"continue\" is not allowed in gdb nested in the jdb.");
0458: out("use jret to return to the jdb");
0459: break;
0460: }
0461: }
0462:
0463: /**
0464: * @return Wheather or not the jdb/gdb switching is ready.
0465: */
0466: boolean IsDebuggerSwitchingInitialized() {
0467: return debuggerSwitchingInitialized;
0468: }
0469:
0470: /**
0471: * Prepare jdb and gdb for j2c and c2j debugger switching.
0472: */
0473: void preparingDebuggerSwitching() {
0474: if (IsDebuggerSwitchingInitialized()) {
0475: out("debuggers were previously initialized for switching.");
0476: return;
0477: }
0478:
0479: // try to initialize the DebugHelper
0480: try {
0481: runAndWaitJDB(
0482: "eval java.lang.Class.forName(\"DebugHelper\")\n",
0483: "java.lang.Class.forName\\(\"DebugHelper\"\\) = \"class DebugHelper\"");
0484: runAndWaitJDB("eval DebugHelper.init()\n",
0485: "DebugHelper.init\\(\\) = <void value>");
0486: } catch (IOException e) {
0487: err("failed in executing initj for jdb\n");
0488: return;
0489: }
0490:
0491: // get debugee process id through the DebugHelper Code.
0492: int debugeeProcessID;
0493: try {
0494: Matcher m = runAndWaitJDB(
0495: "print DebugHelper.getProcessID()\n",
0496: "DebugHelper.getProcessID\\(\\) = (\\d+)");
0497: debugeeProcessID = Integer.parseInt(m.group(1));
0498: } catch (IOException e) {
0499: err("failed in getting debugged process id\n");
0500: return;
0501: }
0502:
0503: // gdb - attach the debugee
0504: try {
0505: String gdbCommand = "gdb -nw -quiet --pid "
0506: + debugeeProcessID + " -ex continue " + gdbOptions;
0507: gdbHandler = new SlaveProcessHandler("gdb", gdbCommand);
0508: gdbHandler.requestExpect("Continuing");
0509: gdbHandler.start();
0510: gdbHandler.waitForExpect();
0511: out("gdb is initialized.");
0512: sendUserMessage("\n"); // trigger 'jdb' prompt.
0513: } catch (IOException e) {
0514: err("failed in \n");
0515: return;
0516: }
0517:
0518: debuggerSwitchingInitialized = true;
0519: }
0520:
0521: /**
0522: * Send a message to the jdb , and wait until the jdb sends back an expected
0523: * message.
0524: *
0525: * @param msg The message to send to the jdb.
0526: * @param expect The message to be expected from the jdb.
0527: * @return The matched result for the expected messsage.
0528: */
0529: Matcher runAndWaitJDB(String msg, String expect) throws IOException {
0530: return jdbHandler.runAndWait(msg, expect);
0531: }
0532:
0533: /**
0534: * Send a message to the jdb, and wait for a number of expected messages from
0535: * the jdb.
0536: *
0537: * @param msg The message to send to jdb.
0538: * @param expects The list of expected message message.
0539: * @return The response from the jdb.
0540: */
0541: DebuggerResponse runAndWaitJDB(String msg, String[] expects)
0542: throws IOException {
0543: return jdbHandler.runAndWait(msg, expects);
0544: }
0545:
0546: /**
0547: * Send a message to the gdb process, and wait until the gdb sends back an
0548: * expected message.
0549: *
0550: * @param msg The message to send.
0551: * @param expect The message to be expected from the gdb.
0552: */
0553: Matcher runAndWaitGDB(String msg, String expect) throws IOException {
0554: return gdbHandler.runAndWait(msg, expect);
0555: }
0556:
0557: /**
0558: * Send a message to the jdb process, and wait until the gdb sends back an
0559: * expected message.
0560: *
0561: * @param msg The message to send.
0562: * @param expect The message to be expected from the slave process.
0563: */
0564: Matcher runJDBAndWaitGDB(String msg, String expect)
0565: throws IOException {
0566: return jdbHandler.runAndWait(msg, expect, gdbHandler);
0567: }
0568:
0569: /**
0570: * Send a message to the gdb process, and wait until the jdb sends back an
0571: * expected message.
0572: *
0573: * @param msg The message to send.
0574: * @param expect The list of the messages expected from the jdb.
0575: */
0576: Matcher runGDBAndWaitJDB(String msg, String expect)
0577: throws IOException {
0578: return gdbHandler.runAndWait(msg, expect, jdbHandler);
0579: }
0580:
0581: /**
0582: * Print a message to the console.
0583: *
0584: * @param msg The message to print.
0585: */
0586: void out(String msg) {
0587: try {
0588: userOutput.write(msg.getBytes());
0589: userOutput.flush();
0590: } catch (IOException e) {
0591: e.printStackTrace();
0592: }
0593: }
0594:
0595: /**
0596: * Print an error message to the console.
0597: *
0598: * @param msg The error message to print.
0599: */
0600: void err(String msg) {
0601: try {
0602: String msgAll = "\n[blink] error: " + msg + "\n";
0603: userError.write(msgAll.getBytes());
0604: userError.flush();
0605: } catch (IOException e) {
0606: e.printStackTrace();
0607: }
0608: }
0609:
0610: /**
0611: * Switch to the new debugger state.
0612: *
0613: * @param newStatus The requested new state.
0614: */
0615: synchronized void changeDebugStatus(DebugerControlStatus newStatus) {
0616: if (newStatus == debugerControlStatus) {
0617: return;
0618: }
0619: boolean success = false;
0620: switch (debugerControlStatus) {
0621: case NONE:
0622: success = newStatus == DebugerControlStatus.JDB
0623: || newStatus == DebugerControlStatus.GDB;
0624: break;
0625: case JDB:
0626: success = newStatus == DebugerControlStatus.NONE
0627: || newStatus == DebugerControlStatus.GDB_IN_JDB;
0628: break;
0629: case GDB:
0630: success = newStatus == DebugerControlStatus.NONE
0631: || newStatus == DebugerControlStatus.JDB_IN_GDB;
0632: break;
0633: case JDB_IN_GDB:
0634: success = newStatus == DebugerControlStatus.GDB;
0635: break;
0636: case GDB_IN_JDB:
0637: success = newStatus == DebugerControlStatus.JDB;
0638: break;
0639: }
0640:
0641: if (success) {
0642: debugerControlStatus = newStatus;
0643: } else {
0644: err("Not allowed transition:" + debugerControlStatus + "->"
0645: + newStatus);
0646: Thread.dumpStack();
0647: }
0648: }
0649:
0650: /**
0651: * @return The debugger control status.
0652: */
0653: DebugerControlStatus getControlStatus() {
0654: return debugerControlStatus;
0655: }
0656:
0657: /**
0658: * Show the current Blink status.
0659: */
0660: void printStatus() {
0661: out("control: " + debugerControlStatus);
0662: }
0663:
0664: /**
0665: * Send a message to the currently chose debugger.
0666: *
0667: * @param msg The message.
0668: */
0669: synchronized void sendUserMessage(String msg) throws IOException {
0670: switch (debugerControlStatus) {
0671: case NONE:
0672: err("Not sure where to send this messsage:" + msg);
0673: break;
0674: case JDB:
0675: case JDB_IN_GDB:
0676: jdbHandler.sendMessage(msg);
0677: break;
0678: case GDB:
0679: case GDB_IN_JDB:
0680: gdbHandler.sendMessage(msg);
0681: break;
0682: default:
0683: err("Unknown debug control status.");
0684: break;
0685: }
0686: }
0687:
0688: /**
0689: * Notification of the death of the slave process.
0690: *
0691: * @param slave The dead process handler.
0692: */
0693: private synchronized void onSlaveProcessDeath(
0694: SlaveProcessHandler slave) {
0695: if (slave == jdbHandler) {
0696: out("jdb finished...");
0697: } else if (slave == gdbHandler) {
0698: out("gdb finished...");
0699: }
0700: allSlaveProcessLive = false;
0701: }
0702:
0703: /**
0704: * Process the output message from the slave process.
0705: *
0706: * @param slave The handler of the slave process.
0707: * @param buf The byte buffer.
0708: * @param len The length of the data in the buffer.
0709: */
0710: private void onSlaveProcessOut(SlaveProcessHandler slave,
0711: byte[] buf, int len) {
0712: try {
0713: userOutput.write(buf, 0, len);
0714: userOutput.flush();
0715: } catch (IOException e) {
0716: e.printStackTrace();
0717: }
0718: }
0719:
0720: /**
0721: * Process the error message form the slave process.
0722: *
0723: * @param slave The handler of the slave process.
0724: * @param buf The byte buffer.
0725: * @param len The length of the data in the buffer.
0726: */
0727: private void onSlaveProceeErr(SlaveProcessHandler slave,
0728: byte[] buf, int len) {
0729: try {
0730: userError.write(buf, 0, len);
0731: userError.flush();
0732: } catch (IOException e) {
0733: e.printStackTrace();
0734: }
0735: }
0736:
0737: /**
0738: * The process break point hit notification from the slave.
0739: * @param slave The slave process that gets the break point.
0740: */
0741: private synchronized void onSlaveProcessBreakPointHit(
0742: SlaveProcessHandler slave) {
0743: if (slave == jdbHandler) {
0744: changeDebugStatus(DebugerControlStatus.JDB);
0745: } else if (slave == gdbHandler) {
0746: changeDebugStatus(DebugerControlStatus.GDB);
0747: }
0748: }
0749:
0750: /**
0751: * The jdb hits the System.loadLibrary event.
0752: * @param slave The slave process that gets the System.loadLibrary event.
0753: */
0754: private synchronized void onSlaveLoadLibrary(
0755: SlaveProcessHandler slave) {
0756: if (slave != jdbHandler) {
0757: return;
0758: }
0759:
0760: new Thread("loadLibraryHandler") {
0761: public void run() {
0762: try {
0763: changeDebugStatus(DebugerControlStatus.JDB);
0764: runAndWaitJDB(
0765: "clear java.lang.System.loadLibrary\n",
0766: "Removed: breakpoint java.lang.System.loadLibrary");
0767: runAndWaitJDB("step up\n", "Step completed:");
0768: if (!IsDebuggerSwitchingInitialized()) {
0769: preparingDebuggerSwitching();
0770: }
0771:
0772: if (debuggerCommand.HasDeferredBreakPoint()) {
0773: debuggerCommand.HandleDeferredBreakPoint();
0774: }
0775:
0776: runAndWaitJDB(
0777: "stop in java.lang.System.loadLibrary\n",
0778: "Set breakpoint java.lang.System.loadLibrary");
0779: jdbHandler.sendMessage("cont\n");
0780: changeDebugStatus(DebugerControlStatus.NONE);
0781: } catch (IOException e) {
0782:
0783: }
0784: }
0785: }.start();
0786: }
0787:
0788: /**
0789: * Release any system resource create for the debugging. For instance, we want
0790: * to ensure the jdb and gdb are terminated at the end of this method.
0791: */
0792: private void ensureResourceRelease() {
0793: if (jdbHandler != null) {
0794: jdbHandler.finish();
0795: }
0796: if (gdbHandler != null) {
0797: gdbHandler.finish();
0798: }
0799: }
0800:
0801: /**
0802: * The jdb break point hit event pattern.
0803: */
0804: private final static Pattern jdbBreakPointHitPattern = Pattern
0805: .compile("Breakpoint hit: \\\"thread=([^\"]+)\\\", ([^,]+), line=([0-9,]+) bci=([0-9]+)");
0806:
0807: /**
0808: * The gdb break point hit event pattern.
0809: */
0810: private final static Pattern gdbBreakPointHitPattern = Pattern
0811: .compile("Breakpoint [0-9]+, ([^ ]+)");
0812:
0813: /**
0814: * A slave process handler specifically for stream I/O and the process ending.
0815: * This handler is an interface between the Jeannie debugger and its slave
0816: * processes. This handler creates three thread to monitor the process state,
0817: * the process input stream and the process output stream. Mostly all these
0818: * threads will be blocked, not consuming CPU cycle too much.
0819: */
0820: private class SlaveProcessHandler {
0821:
0822: /**
0823: * The name of this handler.
0824: */
0825: private final String name;
0826:
0827: /**
0828: * The command line to create the native process.
0829: */
0830: private final String command;
0831:
0832: /**
0833: * The native process.
0834: */
0835: private Process process;
0836:
0837: /**
0838: * The buffered writer to the process.
0839: */
0840: private BufferedWriter out;
0841:
0842: /**
0843: * A thread to redirect the process output message to the Jeannie debugger.
0844: */
0845: private Thread outRedirector;
0846:
0847: /**
0848: * A thread to redirect the process error message to the Jeannie debugger.
0849: */
0850: private Thread errRedirector;
0851:
0852: /**
0853: * If set, the output from the slave will be ignored.
0854: */
0855: private boolean skipBytesInRedirection;
0856:
0857: /**
0858: * The expected message from the output stream.
0859: */
0860: private Pattern[] expectedMessagePatterns;
0861:
0862: /**
0863: * The result of expected message matching;
0864: */
0865: private DebuggerResponse debuggerResponse;
0866:
0867: /**
0868: * This buffer keeps a output line whose length is less then 1024. The
0869: * process handler scans the output from the target process, and try to
0870: * recognize a line with less than 1024 bytes. If the line over flows, then
0871: * this handler will ignore the line.
0872: */
0873: private final byte[] lineBuffer = new byte[1024];
0874:
0875: /**
0876: * This state keeps track of the current line output. 0, ...,
0877: * (jdbLineOutputBuffer.length-1): reading the current line.
0878: * jdbLineOutputBuffer.length, ...: ignoring the current line.
0879: */
0880: private int numBytesInLineBuffer;
0881:
0882: /**
0883: * The current process state.
0884: */
0885: private ProcessState state = ProcessState.NOT_CREATED;
0886:
0887: /**
0888: * @param jeannieDebugger The Jeannie master debugger.
0889: * @param name The name of the slave process.
0890: * @param command The command line to create the slave process.
0891: */
0892: SlaveProcessHandler(String name, String command) {
0893: this .name = name;
0894: this .command = command;
0895: }
0896:
0897: /**
0898: * Start the monitoring thread.
0899: */
0900: void start() {
0901: assert state == ProcessState.NOT_CREATED;
0902: try {
0903: process = Runtime.getRuntime().exec(command);
0904: state = ProcessState.LIVE;
0905: OutputStream os = process.getOutputStream();
0906: OutputStreamWriter ow = new OutputStreamWriter(os);
0907: out = new BufferedWriter(ow);
0908: outRedirector = new Thread(name + "-out") {
0909: public void run() {
0910: handleOutputStream();
0911: }
0912: };
0913: errRedirector = new Thread(name + "-err") {
0914: public void run() {
0915: handleErrorStream();
0916: }
0917: };
0918: outRedirector.start();
0919: errRedirector.start();
0920: } catch (IOException e) {
0921: err("failed in executing initj for gdb\n");
0922: e.printStackTrace();
0923: }
0924:
0925: assert process != null & outRedirector != null
0926: & errRedirector != null;
0927: }
0928:
0929: /**
0930: * Complete this slave process.
0931: */
0932: void finish() {
0933: switch (state) {
0934: case NOT_CREATED:
0935: state = ProcessState.DEAD;
0936: break;
0937: case LIVE:
0938: state = ProcessState.DEAD;
0939: process.destroy();
0940: break;
0941: case DEAD:
0942: break;
0943: default:
0944: assert false;
0945: break;
0946: }
0947:
0948: }
0949:
0950: /**
0951: * The background routine to redirect the slave output to the Jeannie
0952: * debugger.
0953: */
0954: private void handleOutputStream() {
0955: InputStream is = process.getInputStream();
0956: final byte[] buf = new byte[4096];
0957: try {
0958: while (true) {
0959: if (state == ProcessState.DEAD)
0960: break;
0961: int nread = is.read(buf);
0962: if (nread == -1)
0963: break;
0964: if (state == ProcessState.DEAD)
0965: break;
0966:
0967: boolean OKToRedirect;
0968: synchronized (this ) {
0969: OKToRedirect = !skipBytesInRedirection;
0970: }
0971: if (OKToRedirect)
0972: onSlaveProcessOut(this , buf, nread);
0973: checkOutputLine(buf, nread);
0974: }
0975: } catch (IOException e) {
0976: if (state == ProcessState.LIVE) {
0977: System.err.println("IO error in piping thread"
0978: + name);
0979: e.printStackTrace();
0980: }
0981: } finally {
0982: synchronized (this ) {
0983: if (state == ProcessState.LIVE) {
0984: state = ProcessState.DEAD;
0985: onSlaveProcessDeath(this );
0986: }
0987: }
0988: }
0989: }
0990:
0991: /**
0992: * The background routine to redirect the slave error stream output to the
0993: * Jeannie debugger.
0994: */
0995: private void handleErrorStream() {
0996: InputStream is = process.getErrorStream();
0997: final byte[] buf = new byte[4096];
0998: try {
0999: while (true) {
1000: if (state == ProcessState.DEAD)
1001: break;
1002: int nread = is.read(buf);
1003: if (nread == -1) {
1004: synchronized (this ) {
1005: state = ProcessState.DEAD;
1006: onSlaveProcessDeath(this );
1007: }
1008: break;
1009: }
1010: if (state == ProcessState.DEAD)
1011: break;
1012: onSlaveProceeErr(this , buf, nread);
1013: }
1014: } catch (IOException e) {
1015: if (state == ProcessState.LIVE) {
1016: System.err.println("IO error in piping thread"
1017: + name);
1018: e.printStackTrace();
1019: }
1020: } finally {
1021: synchronized (this ) {
1022: if (state == ProcessState.LIVE) {
1023: state = ProcessState.DEAD;
1024: onSlaveProcessDeath(this );
1025: }
1026: }
1027: }
1028: }
1029:
1030: /**
1031: * Send a message to the output stream.
1032: *
1033: * @param msg The message.
1034: */
1035: void sendMessage(String msg) throws IOException {
1036: out.write(msg);
1037: out.flush();
1038: }
1039:
1040: /**
1041: * Check the read data for the output line event.
1042: *
1043: * @param buf The data buffer.
1044: * @param len The buffer length.
1045: */
1046: void checkOutputLine(byte[] buf, int len) {
1047: for (int i = 0; i < len; i++) {
1048: byte c = buf[i];
1049: if (numBytesInLineBuffer < lineBuffer.length) {
1050: if (((char) c) == '\n') {
1051: handleOutputLine(new String(lineBuffer, 0,
1052: numBytesInLineBuffer));
1053: numBytesInLineBuffer = 0;
1054: } else {
1055: lineBuffer[numBytesInLineBuffer++] = c;
1056: }
1057: } else {
1058: if (((char) c) == '\n') {
1059: numBytesInLineBuffer = 0;
1060: } else {
1061: numBytesInLineBuffer++;
1062: }
1063: }
1064: }
1065: }
1066:
1067: /**
1068: * Handle a line from process output.
1069: *
1070: * @param line The line output from the process.
1071: */
1072: void handleOutputLine(String line) {
1073: if (line.equals(""))
1074: return;
1075:
1076: // handle any expected message request at first.
1077: checkExpect(line);
1078:
1079: Matcher mjdbbp = jdbBreakPointHitPattern.matcher(line);
1080: if (mjdbbp.find()) {
1081: String method = mjdbbp.group(2);
1082: if (method.startsWith("java.lang.System.loadLibrary")) {
1083: onSlaveLoadLibrary(this );
1084: } else if (method.startsWith("DebugHelper")) {
1085:
1086: } else {
1087: onSlaveProcessBreakPointHit(this );
1088: }
1089: }
1090:
1091: Matcher mgdbbp = gdbBreakPointHitPattern.matcher(line);
1092: if (mgdbbp.find()) {
1093: onSlaveProcessBreakPointHit(this );
1094: }
1095: }
1096:
1097: /**
1098: * Send a message to the slave process, and wait until the slave sends back
1099: * an expected message.
1100: *
1101: * @param msg The message to send.
1102: * @param expect The message to be expected from the slave process.
1103: */
1104: Matcher runAndWait(String msg, String expect)
1105: throws IOException {
1106: requestExpect(expect);
1107: sendMessage(msg);
1108: waitForExpect();
1109: return getExpectedMatcher();
1110: }
1111:
1112: /**
1113: * Send a message to the slave process, and wait until the slave sends back
1114: * an expected message.
1115: *
1116: * @param msg The message to send.
1117: * @param expects The message to be expected from the slave process.
1118: */
1119: DebuggerResponse runAndWait(String msg, String[] expects)
1120: throws IOException {
1121: requestExpect(expects);
1122: sendMessage(msg);
1123: waitForExpect();
1124: return getDebuggerResponse();
1125: }
1126:
1127: /**
1128: * Send a message to this slave process, and wait until the other slave
1129: * process respond with the expected message.
1130: *
1131: * @param msg The message to send.
1132: * @param expect The expected message.
1133: * @param other The other slave process.
1134: */
1135: Matcher runAndWait(String msg, String expect,
1136: SlaveProcessHandler responder) throws IOException {
1137: responder.requestExpect(expect);
1138: sendMessage(msg);
1139: responder.waitForExpect();
1140: return responder.getExpectedMatcher();
1141: }
1142:
1143: /**
1144: * Check if the received bytes says that the expected message was received.
1145: *
1146: * @param line The line output from the output stream.
1147: */
1148: private synchronized void checkExpect(String line) {
1149: if (expectedMessagePatterns == null)
1150: return;
1151:
1152: for (int i = 0; i < expectedMessagePatterns.length; i++) {
1153: Pattern p = expectedMessagePatterns[i];
1154: Matcher m = p.matcher(line);
1155: if (m.find()) {
1156: this .expectedMessagePatterns = null;
1157: this .debuggerResponse = new DebuggerResponse(i, m);
1158: this .notify();
1159: return;
1160: }
1161: }
1162: }
1163:
1164: /**
1165: * Request a message arrival notification.
1166: *
1167: * @param rexpr The regular expression to wait for.
1168: */
1169: synchronized void requestExpect(String rexpr) {
1170: requestExpect(new String[] { rexpr });
1171: }
1172:
1173: /**
1174: *
1175: * @param rexprs The array of regular repressions to wait for.
1176: */
1177: synchronized void requestExpect(String[] exprs) {
1178: int numRegularExpressions = exprs.length;
1179: expectedMessagePatterns = new Pattern[numRegularExpressions];
1180: for (int i = 0; i < numRegularExpressions; i++) {
1181: String rexpr = exprs[i];
1182: expectedMessagePatterns[i] = Pattern.compile(rexpr);
1183: }
1184: skipBytesInRedirection = true;
1185: }
1186:
1187: /**
1188: * Wait until we see the expected message in the stream.
1189: */
1190: synchronized void waitForExpect() {
1191: if (expectedMessagePatterns == null)
1192: return;
1193: try {
1194: this .wait(); // wait until awaken by the checkExpect.
1195: } catch (InterruptedException e) {
1196: }
1197: skipBytesInRedirection = false;
1198: }
1199:
1200: /**
1201: * Return the match object for the expected message.
1202: *
1203: * @return The match object for the regular expression
1204: */
1205: synchronized Matcher getExpectedMatcher() {
1206: assert debuggerResponse != null;
1207: return debuggerResponse.getMatcher();
1208: }
1209:
1210: /**
1211: * Return the debugger's response object.
1212: *
1213: * @return The debugger's response.
1214: */
1215: synchronized DebuggerResponse getDebuggerResponse() {
1216: return debuggerResponse;
1217: }
1218: }
1219:
1220: /**
1221: * A debugger's response to the blink's command.
1222: *
1223: */
1224: public static class DebuggerResponse {
1225:
1226: /**
1227: * The message ID to show which expected message request.
1228: */
1229: final int expectedMessageID;
1230:
1231: /**
1232: * The matched message.
1233: */
1234: final Matcher matchedMessage;
1235:
1236: /**
1237: * @param mid The message id.
1238: * @param msg THe matcher.
1239: */
1240: DebuggerResponse(int mid, Matcher msg) {
1241: expectedMessageID = mid;
1242: matchedMessage = msg;
1243: }
1244:
1245: /**
1246: * @return The expected message ID.
1247: */
1248: int getExpectedMessageID() {
1249: return expectedMessageID;
1250: }
1251:
1252: /**
1253: * @return The matcher object.
1254: */
1255: Matcher getMatcher() {
1256: return matchedMessage;
1257: }
1258: }
1259: }
|