/* * Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package nsk.share.jdb; import nsk.share.*; import nsk.share.jpda.*; import java.util.*; import java.io.*; import java.util.regex.*; /** * Wrapper of jdb.s * This class provides abilities to launch it, to send command, * to read reply on the command, to set breakpoint on entry in debugggee's method. */ public class Jdb extends LocalProcess implements Finalizable { /** File to log stdout stream */ static final String JDB_STDOUT_FILE = "jdb.stdout"; /** File to log stderr stream */ static final String JDB_STDERR_FILE = "jdb.stderr"; /** File to log input jdb's commands */ static final String JDB_COMMANDS_FILE = "jdb.commands"; /** File to log emulated jdb session composed from commands and jdb replies on them */ static final String JDB_SESSION_FILE = "jdb.session"; /** Pattern for message of listening at address. */ public static final String LISTENING_AT_ADDRESS = "Listening at address:"; /** Pattern for message of a breakpoint hit. */ public static final String BREAKPOINT_HIT = "Breakpoint hit:"; /** Pattern for message of an application exit. */ public static final String APPLICATION_EXIT = "The application exited"; /** Pattern for message of an application disconnect. */ public static final String APPLICATION_DISCONNECTED = "The application has been disconnected"; /** Pattern for message of connector name in the supported connectors list. */ public static final String SUPPORTED_CONNECTOR_NAME = "Connector:"; /** Pattern for message of transport name in the supported connectors list. */ public static final String SUPPORTED_TRANSPORT_NAME = "Transport: "; /** This is jdb's prompt when debuggee is not started nor suspended after breakpoint */ public static final String SIMPLE_PROMPT = "> "; public static final String lineSeparator = System.getProperty("line.separator"); /** Internal streams handlers */ private static PrintStream jdbStdinWriter; private static JdbStdoutReader jdbStdoutReader; private static JdbStderrReader jdbStderrReader; private static PrintStream fout; private static PrintStream flog; private static PrintStream fin; /** Particular ident in a compound prompt, or null if any. */ private String compoundPromptIdent = null; /** Launcher that creates this Jdb object. */ private static Launcher launcher = null; /** Internal buffer to save all not-null string from jdb stdout */ volatile private static StringBuffer stdoutBuffer = new StringBuffer(); volatile private Object startNotify = new Object(); /** Returns Launcher that created this Jdb object. */ public static Launcher getLauncher() { return launcher; } public void finalizeAtExit() throws Throwable { finalize(); } public void finalize() throws Throwable { if (fout != null) { // fout.flush(); fout.close(); } if (flog != null) { flog.close(); } if (fin != null) { fin.close(); } if (jdbStdoutReader != null) { jdbStdoutReader.close(); } if (jdbStderrReader != null) { jdbStderrReader.close(); } super.finalize(); } /** Create Jdb object. */ public Jdb (Launcher launcher) { super(); this.launcher = launcher; } /** Set particular ident for compound prompt; or null for any ident. */ void setCompoundPromptIdent(String ident) { compoundPromptIdent = ident; } /** * Launch jdb with options defined in launchCmdArgs. * Full path to jdb must be defined as first element in launchCmdArgs. */ public void launch(String[] launchCmdArgs) throws IOException { super.launch(launchCmdArgs); redirectStreams(); } /** * Launch jdb with options defined in launchCmdLine. * Full path to jdb must be defined as first token in launchCmdLine. */ public void launch(String launchCmdLine) throws IOException { super.launch(launchCmdLine); redirectStreams(); } /** * Gets stdin, stdout, stderr streams of the jdb's process and * redirects them to special streams handlers. */ private void redirectStreams() { OutputStream jdbStdin = this.getStdin(); if (jdbStdin == null) { throw new Failure("jdb stdin after launching is null"); } jdbStdinWriter = new PrintStream(jdbStdin, true); String fileStdout = getLauncher().getJdbArgumentHandler().getWorkDir() + File.separator + JDB_STDOUT_FILE; InputStream jdbStdout = this.getStdout(); if (jdbStdout == null) { throw new Failure("jdb stdout after launching is null"); } launcher.getLog().display("Creating file for jdb stdout stream: " + fileStdout); try { fout = new PrintStream(new BufferedOutputStream(new FileOutputStream(fileStdout))); } catch (Exception e) { e.printStackTrace(getLauncher().getLog().getOutStream()); throw new Failure("Caught unexpected exception while creating file for jdb stdout stream: " + e); } String fileCommands = getLauncher().getJdbArgumentHandler().getWorkDir() + File.separator + JDB_COMMANDS_FILE; try { fin = new PrintStream(new BufferedOutputStream(new FileOutputStream(fileCommands))); } catch (Exception e) { e.printStackTrace(getLauncher().getLog().getOutStream()); throw new Failure("Caught unexpected exception while creating file for jdb input commands: " + e); } String fileSession = getLauncher().getJdbArgumentHandler().getWorkDir() + File.separator + JDB_SESSION_FILE; launcher.getLog().display("Creating file for jdb session: " + fileSession); try { flog = new PrintStream(new BufferedOutputStream(new FileOutputStream(fileSession))); } catch (Exception e) { e.printStackTrace(getLauncher().getLog().getOutStream()); throw new Failure("Caught unexpected exception while creating file for jdb session: " + e); } String fileStderr = getLauncher().getJdbArgumentHandler().getWorkDir() + File.separator + JDB_STDERR_FILE; InputStream jdbStderr = this.getStderr(); if (jdbStderr == null) { throw new Failure("jdb stderr after launching is null"); } jdbStdoutReader = new JdbStdoutReader(this); startReader(jdbStdoutReader); jdbStderrReader = new JdbStderrReader(this, fileStderr); startReader(jdbStderrReader); } /** Starts reading threads for jdb streams. */ private void startReader (Thread reader) { long max = getLauncher().getJdbArgumentHandler().getWaitTime() * 60 * 1000; // maximum time to wait. boolean notified = false; synchronized (startNotify) { reader.start(); try { startNotify.wait(max); notified = true; } catch (InterruptedException ie) { ie.printStackTrace(getLauncher().getLog().getOutStream()); throw new Failure("Caught InterruptedException while waiting for start of : " + reader, ie); } } if (!notified) { throw new Failure("Main thread was not notified during " + max + " milliseconds" + "\n\t waiting for start of : " + reader); } } /** * Waits for given stream reader of the jdb's process to finish * or interrupts after given timeout. */ private void waitForReader(Thread reader, long timeMillisec) { if (reader != null) { try { reader.join(timeMillisec); } catch (InterruptedException ie) { ie.printStackTrace(getLauncher().getLog().getOutStream()); throw new Failure("Caught interrupted exception while waiting for reader finished:\n\t" + ie); } if (reader.isAlive()) { getLauncher().getLog().display("Interrupting reader not finished for timeout: " + timeMillisec + " millisec"); reader.interrupt(); } } } /** * Waits for all readers of redirected streams of the jdb's process * to finish. */ private void waitForAllReaders(long timeMillisec) { waitForReader(jdbStdoutReader, timeMillisec); waitForReader(jdbStderrReader, timeMillisec); } /** * Wait until the jdb process shutdown or crash * and all redirected stream readers finished. */ public int waitFor() throws InterruptedException { int exitCode = super.waitFor(); waitForAllReaders(0); return exitCode; } /** * Wait until the process shutdown or crash for given timeout in milliseconds, * and all redirected stream readers finished. * Returns LocalProcess.PROCESS_IS_ALIVE if process is not terminated * after timeout. */ public int waitFor(long timeMillisec) throws InterruptedException { int exitCode = super.waitFor(timeMillisec); if (exitCode != LocalProcess.PROCESS_IS_ALIVE) { waitForAllReaders(timeMillisec); } return exitCode; } /** * Writes jdbCommand to jdb's input stream. */ public synchronized void sendCommand(String jdbCommand) { if (terminated()) { throw new Failure("Attempt to send command :" + jdbCommand + "\t to terminated jdb."); } if (jdbCommand != null) { String logCmd; if (!jdbCommand.endsWith(lineSeparator)) { logCmd = jdbCommand; jdbCommand += lineSeparator; } else { // we don't want to log the line separator logCmd = jdbCommand.substring(0, jdbCommand.length() - lineSeparator.length()); } launcher.getLog().display("Sending command: " + logCmd); jdbStdinWriter.print(jdbCommand); jdbStdinWriter.flush(); synchronized(flog) { flog.print(/*LOG_COMMAND_PREFIX +*/ jdbCommand); flog.flush(); } fin.print(jdbCommand); fin.flush(); if (jdbStdinWriter.checkError()) { throw new Failure("Unexpected IO error while writing command <" + jdbCommand + "> to jdb stdin stream"); } } } /** * Sends command to jdb's input stream, waits for compound promt received, * and then returns reply from jdb's output stream. * * @param command string representing full command with all arguments if any. */ public String[] receiveReplyFor(String command) { return receiveReplyFor(command, true); } /** * Sends command to jdb's input stream, waits for promt received, * and then returns reply from jdb's output stream. * * @param command string representing full command with all arguments if any. * @param compoundPromptOnly read output until compound prompt is found. */ public String[] receiveReplyFor(String command, boolean compoundPromptOnly) { return receiveReplyFor(command, compoundPromptOnly, 1); } /** * Sends command to jdb's input stream, waits for given number of promts received, * and then returns reply from jdb's output stream. * * @param command string representing full command with all arguments if any. * @param compoundPromptOnly read output until compound prompt is found. * @param count number of prompt instances to found. */ public String[] receiveReplyFor(String command, boolean compoundPromptOnly, int count) { if (command == null) { return null; } int startPos = stdoutBuffer.length(); sendCommand(command); return receiveReply(startPos, compoundPromptOnly, count); } /** * Sends command to jdb's input stream, waits for specified message to be received, * and then returns reply from jdb's output stream. * * @param command string representing full command with all arguments if any. * @param waitMsg string representing the message that must be sent back before returing. */ public String[] receiveReplyForWithMessageWait(String command, String waitMsg) { if (command == null) { return null; } int startPos = stdoutBuffer.length(); sendCommand(command); waitForMessage(startPos, waitMsg); return receiveReply(startPos, true, 1); } /** * Waits for compound prompt and returns reply from jdb stdout * beginning from startPos in the stdoutBuffer. * * @param startPos start position for search in stdoutBuffer. */ public String[] receiveReply(int startPos) { return receiveReply(startPos, true); } /** * Waits for particular prompt and returns reply from jdb stdout * beginning from startPos in the stdoutBuffer. * * @param startPos start position for search in stdoutBuffer. * @param compoundPromptOnly waits for compound prompt only. */ public String[] receiveReply(int startPos, boolean compoundPromptOnly) { return receiveReply(startPos, compoundPromptOnly, 1); } /** * Waits for count number of prompts and returns reply from jdb stdout * beginning from startPos in the stdoutBuffer. * * @param startPos start position for search in stdoutBuffer. * @param compoundPromptOnly waits for compound prompt only. * @param count number of prompt instances to wait for. */ public String[] receiveReply(int startPos, boolean compoundPromptOnly, int count) { nsk.share.Failure e = null; try { waitForPrompt(startPos, compoundPromptOnly, count); } catch (nsk.share.Failure nsf) { e = nsf; launcher.getLog().display("receiveReply FAILED due to \"" + e + "\"."); launcher.getLog().display("Pending reply output follows:"); } String reply = stdoutBuffer.substring(startPos, stdoutBuffer.length()); String[] replyArr = toStringArray(reply); // Send reply to the logfile. This complements sendCommand(), which does the same. for (int i = 0; i < replyArr.length; i++) { launcher.getLog().display("reply[" + i + "]: " + replyArr[i]); } if (e != null) throw e; return replyArr; } /** * Reads JDB_STDOUT_FILE file until prompt is found in the stdoutBuffer. * * @param startPos start position for search in stdoutBuffer. * @param compoundPromptOnly search for compound prompt only. * @throws Failure if prompt is not encountered during WaitTime. * @return number of prompt instances really found. */ public int waitForPrompt(int startPos, boolean compoundPromptOnly) { return waitForPrompt(startPos, compoundPromptOnly, 1); } /** * Reads JDB_STDOUT_FILE file until prompt is found in the stdoutBuffer * count times. * * @param startPos start position for search in stdoutBuffer. * @param compoundPromptOnly search for compound prompt only. * @throws Failure if prompt is not encountered count times during WaitTime. * @return number of prompt instances actually found * * @see #setCompoundPromptIdent(String) */ public int waitForPrompt(int startPos, boolean compoundPromptOnly, int count) { long delta = 200; // time in milliseconds to wait at every iteration. long total = 0; // total time has waited. long max = getLauncher().getJdbArgumentHandler().getWaitTime() * 60 * 1000; // maximum time to wait. if (count <= 0) { throw new TestBug("Wrong number of prompts count in Jdb.waitForPrompt(): " + count); } Object dummy = new Object(); while ((total += delta) <= max) { int found = 0; // check if compound prompt is found { found = findPrompt(stdoutBuffer, true, startPos); if (found >= count) { return found; } } // check also if simple prompt is found if (!compoundPromptOnly) { found += findPrompt(stdoutBuffer, false, startPos); if (found >= count) { return found; } } // exit loop when a debugged application exited if (stdoutBuffer.indexOf(APPLICATION_EXIT) >= 0 || stdoutBuffer.indexOf(APPLICATION_DISCONNECTED) >= 0) { return found; } else if (startPos > 0 && !jdbStdoutReader.isAlive()) { return found; } // sleep for awhile synchronized(dummy) { try { dummy.wait(delta); } catch (InterruptedException ie) { ie.printStackTrace(getLauncher().getLog().getOutStream()); throw new Failure("Caught interrupted exception while waiting for jdb prompt:\n\t" + ie); } } } Pattern debuggeeExceptionPattern = Pattern.compile("Exception occurred: (?\\S+) \\(uncaught\\)"); String buf = stdoutBuffer.toString(); Matcher m = debuggeeExceptionPattern.matcher(buf); if (m.find(startPos)) { throw new DebuggeeUncaughtException(m.group("DebuggeeException")); } String times = (count > 1 ? count + " times " : ""); throw new Failure("Prompt is not received " + times + "during " + total + " milliseconds."); } /** * Reads JDB_STDOUT_FILE file until expected message is found in the stdoutBuffer. * * @param startPos start position for search in stdoutBuffer. * @throws Failure if expected message is not encountered during WaitTime. * @return number of messages actually found */ public int waitForMessage(int startPos, String message) { long delta = 200; // time in milliseconds to wait at every iteration. long total = 0; // total time has waited. long max = getLauncher().getJdbArgumentHandler().getWaitTime() * 60 * 1000; // maximum time to wait. Object dummy = new Object(); while ((total += delta) <= max) { int found = 0; // search for message { found = findMessage(startPos, message); if (found > 0) { return found; } } // exit loop when a debugged application exited. if (stdoutBuffer.indexOf(APPLICATION_EXIT) >= 0 || stdoutBuffer.indexOf(APPLICATION_DISCONNECTED) >= 0) { return found; } else if (startPos > 0 && !jdbStdoutReader.isAlive()) { return found; } // spleep for awhile synchronized(dummy) { try { dummy.wait(delta); } catch (InterruptedException ie) { ie.printStackTrace(getLauncher().getLog().getOutStream()); throw new Failure("Caught interrupted exception while waiting for jdb reply:\n\t" + ie); } } } // If we never recieved the expected reply, display a warning, and also // display what we did recieve. This is accomplished by calling receiveReply(). Log log = getLauncher().getLog(); log.display("WARNING: message not recieved: " + message); log.display("Remaining debugger output follows:"); receiveReply(startPos); throw new Failure("Expected message not received during " + total + " milliseconds:" + "\n\t" + message); } /** * Find message in JDB_STDOUT_FILE file starting from startPos. * * @param startPos start position for search in stdoutBuffer. * @return number of messages actually found */ public int findMessage(int startPos, String message) { int bufLength = stdoutBuffer.length(); int msgLength = message.length(); int found = 0; for (int pos = startPos; pos < bufLength; ) { pos = stdoutBuffer.indexOf(message, pos); if (pos < 0) break; found++; pos += msgLength; } return found; } /** * Searches input lines for jdb prompt of particular kind. * starting from startPos. * The possible prompt kinds are simple prompt "> " and compound prompt, * that looks like '.*\[[0-9]*\] ' regexp on a single line. * For example, 'main[1] ' (see setCompoundPromptIdent(String)). *

* In order to make compatible with jdk prior to 1.4.0 avoid using * java.util.regex classes. * * @return number of prompt instances found * * @see #setCompoundPromptIdent(String) */ int findPrompt(StringBuffer lines, boolean compoundPromptOnly, int startPos) { final String nameDelimiters = "-_"; int noPrompt = -1; // prompt is not found; int simplePrompt = 1; int complexPrompt = 2; int length = lines.length(); int found = 0; // search for simple prompt if (!compoundPromptOnly) { int promptLength = SIMPLE_PROMPT.length(); for (int pos = startPos; pos < length; ) { pos = lines.indexOf(SIMPLE_PROMPT, pos); if (pos < 0) break; found++; pos += promptLength; } return found; } // search for compound prompt StringBuffer prompt = new StringBuffer(100); searching: for (int pos = startPos; pos < length; ) { // skip each simbol not suitable for prompt begin if (!Character.isLetterOrDigit(lines.charAt(pos))) { pos++; continue searching; } // check for compound prompt prompt.setLength(0); // read name (letters or digits or delimiters) while (nameDelimiters.indexOf(lines.charAt(pos)) > 0 || Character.isLetterOrDigit(lines.charAt(pos)) || lines.charAt(pos) == '-' || lines.charAt(pos) == '_') { prompt.append(lines.charAt(pos++)); if (pos >= length) { break searching; } } // read opening '[' if (lines.charAt(pos) != '[') { continue searching; } prompt.append(lines.charAt(pos++)); if (pos >= length) { break searching; } // read number (digits) if (!Character.isDigit(lines.charAt(pos))){ continue searching; } while (Character.isDigit(lines.charAt(pos))) { prompt.append(lines.charAt(pos++)); if (pos >= length) { break searching; } } // read closing ']' if (lines.charAt(pos) != ']') { continue searching; } prompt.append(lines.charAt(pos++)); if (pos >= length) { break searching; } // read last ' ' if (lines.charAt(pos) != ' ') { continue searching; } prompt.append(lines.charAt(pos++)); // check if not particular ident found if (compoundPromptIdent != null && !prompt.toString().startsWith(compoundPromptIdent + "[")) { continue searching; } // compound prompt found found++; } return found; } /** * Splits string which may include line separators to string array. * */ public static String[] toStringArray (String string) { Vector v = new Vector(); int ind; for (ind = 0; ind < string.length(); ) { int i = string.indexOf(lineSeparator, ind); if (i >= 0) { v.add(string.substring(ind, i)); ind = i + lineSeparator.length(); } else { v.add(string.substring(ind)); break; } } String[] result = new String [v.size()]; v.toArray(result); return result; } /** * Set breakpoint for debuggee on method invocation. */ public void setBreakpointInMethod(String methodName) { String nextCommand = JdbCommand.stop_in + methodName; String[] reply = receiveReplyFor(nextCommand); Paragrep grep = new Paragrep(reply); if (grep.find("Unable to set") > 0) { throw new Failure("jdb failed to set breakpoint in method: " + methodName); } if (grep.find("Set breakpoint") <= 0 && grep.find("Deferring breakpoint") <= 0) { throw new Failure("jdb did not set breakpoint in method: " + methodName); } } /** * Set deferred breakpoint for debuggee on method invocation. * This method must be used before command. */ public void setDeferredBreakpointInMethod(String methodName) { String nextCommand = JdbCommand.stop_in + methodName; String[] reply = receiveReplyFor(nextCommand, false); Paragrep grep = new Paragrep(reply); if (grep.find("Unable to set") > 0) { throw new Failure("jdb failed to set deffered breakpoint in method: " + methodName); } if (grep.find("Set breakpoint") <= 0 && grep.find("Deferring breakpoint") <= 0) { throw new Failure("jdb did not set deffered breakpoint in method: " + methodName); } } /** * Returns true if reply contains breakpoint message. */ public boolean isAtBreakpoint(String[] reply) { return isAtBreakpoint(reply, "", ""); } /** * Returns true if reply contains breakpoint message in certain method. */ public boolean isAtBreakpoint(String[] reply, String method) { return isAtBreakpoint(reply, method, ""); } /** * Returns true if reply contains breakpoint message in certain method * and in certain thread id. */ public boolean isAtBreakpoint(String[] reply, String method, String thread) { boolean result = false; Vector v = new Vector(); Paragrep grep = new Paragrep(reply); v.add(BREAKPOINT_HIT); if (method.length() > 0) { v.add(method); } if (thread.length() > 0) { v.add(thread); } if (grep.find(v) > 0) { result = true; } return result; } /** * Load and start execution of given debuggee's class with arguments. */ public void startDebuggeeClass(String classWithArgs) { String[] reply = receiveReplyFor(JdbCommand.run + " " + classWithArgs); // give one more chance to reach breakpoint if (!isAtBreakpoint(getTotalReply(), "main")) { waitForMessage(0, BREAKPOINT_HIT); } } /** * Start execution of pre-loaded debuggee's class. */ public void startDebuggeeClass() { String[] reply = receiveReplyFor(JdbCommand.run); // give one more chance to reach breakpoint if (!isAtBreakpoint(getTotalReply(), "main")) { waitForMessage(0, BREAKPOINT_HIT); } } /** * Returns as string array all id's for a given thread name of threadName. */ public String[] getThreadIdsByName(String threadName) { Vector v = new Vector(); String[] reply = receiveReplyFor(JdbCommand.threads); Paragrep grep = new Paragrep(reply); String[] found = grep.findStrings(threadName); for (int i = 0; i < found.length; i++) { String string = found[i]; // Check for "(java.lang.Thread)" or "(java.lang.VirtualThread)" String searchString = "Thread)"; int j = string.indexOf(searchString); if (j >= 0) { j += searchString.length(); // The threadID is right after the thread type String threadId = string.substring(j, string.indexOf(" ", j)); v.add(threadId); } } String[] result = new String[v.size()]; v.toArray(result); return result; } /** * Returns as string array all id's for a given class type of threadType. */ public String[] getThreadIds(String threadType) { if (!threadType.startsWith("(")) { threadType = "(" + threadType; } if (!threadType.endsWith(")")) { threadType = threadType + ")"; } Vector v = new Vector(); String[] reply = receiveReplyFor(JdbCommand.threads); Paragrep grep = new Paragrep(reply); String[] found = grep.findStrings(threadType); for (int i = 0; i < found.length; i++) { String string = found[i]; int j = string.indexOf(threadType); if (j >= 0) { j += threadType.length(); String threadId = string.substring(j, string.indexOf(" ", j)); v.add(threadId); } } String[] result = new String [v.size()]; v.toArray(result); return result; } /** * Quit jdb using "quit" command. */ public void quit() { if (!terminated()) { sendCommand(JdbCommand.quit); } } /** * Sends "cont" command up to maxTimes until debuggee exit. */ public void contToExit (int maxTimes) { boolean exited = false; for (int i = 0; i < maxTimes; i++) { if (!terminated()) { String [] reply = receiveReplyFor(JdbCommand.cont); Paragrep grep = new Paragrep(reply); if (grep.find(APPLICATION_EXIT) > 0) { exited = true; break; } } else { exited = true; break; } } if (!exited) { if (terminated()) { exited = true; } else { quit(); throw new Failure("Debuggee did not exit after " + maxTimes + " commands"); } } } /** * Returns string array containing all strings from jdb stdout. */ public String[] getTotalReply() { return toStringArray(stdoutBuffer.toString()); } /** * Prints given message to log files and adds to stdoutBuffer. */ public void logToFile(String s) { synchronized(fout) { fout.print(s); fout.flush(); } synchronized(stdoutBuffer) { stdoutBuffer.append(s); } synchronized(flog) { flog.print(s); flog.flush(); } } /** * Starts jdb with attaching connector. Makes several tries during waitTime * until success. Unsuccessful launches are caused that the debuggee is not yet * ready to accept debugger. */ public static Jdb startAttachingJdb (Launcher launcher, String[] jdbCmdArgs, String message) throws IOException { Jdb jdb = null; long delta = Launcher.DEBUGGEE_START_DELAY; // time in milliseconds to wait at every iteration. long max = getLauncher().getJdbArgumentHandler().getWaitTime() * 60 * 1000; // maximum time to wait. int result = -1; boolean found = false; long start = System.currentTimeMillis(); while (!found && (System.currentTimeMillis() - start)<= max) { jdb = new Jdb(launcher); jdb.launch(jdbCmdArgs); while (!found && (System.currentTimeMillis() - start)<= max) { try { Thread.currentThread().sleep(delta); } catch (InterruptedException ie) { ie.printStackTrace(getLauncher().getLog().getOutStream()); throw new Failure("Caught unexpected InterruptedException while sleep in waiting for debuggee's start:\n\t" + ie); } if (jdb.terminated() || !jdbStdoutReader.isAlive() || stdoutBuffer.indexOf(APPLICATION_EXIT) >= 0 || stdoutBuffer.indexOf(APPLICATION_DISCONNECTED) >= 0) { System.out.println("Unsuccessful launch of attaching jdb. Next try..."); try { jdb.finalize(); } catch (Throwable t) { t.printStackTrace(getLauncher().getLog().getOutStream()); throw new Failure("Caught unexpected error while finalizing jdb: " + t); } break; } else if (stdoutBuffer.length() > 0) { result = stdoutBuffer.indexOf(message); if (result >= 0) { found = true; // exit loop } } } } if (result < 0) { throw new Failure("Launched jdb could not attach to debuggee during " + max + " milliseconds."); } return jdb; } /** * Waits for jdb to print message about listening at address for connection, * and returns this address string. */ public String waitForListeningJdb() { waitForMessage(0, LISTENING_AT_ADDRESS); int msgStart = stdoutBuffer.indexOf(LISTENING_AT_ADDRESS); int msgEnd = stdoutBuffer.indexOf("\n", msgStart); int promptLen = LISTENING_AT_ADDRESS.length(); /* * The LISTENING_AT_ADDRESS string and the terminating "\n" * may or may not be included in the same read so we allow * this message to be terminated by "\n" or NULL. */ if (msgEnd < 0) { msgEnd = stdoutBuffer.length(); } if (msgEnd <= 0 || msgEnd - msgStart <= promptLen) { throw new Failure("Unknown format of message: " + LISTENING_AT_ADDRESS); } int addrStart = msgStart + promptLen; String address = stdoutBuffer.substring(addrStart, msgEnd).trim(); if (address.length() <= 0) { throw new Failure("Empty address in message: " + LISTENING_AT_ADDRESS); } return address; } // ---------------------------------------------- // class JdbStdoutReader extends Thread { private Jdb jdb = null; private InputStream in = null; volatile boolean stop = false; public JdbStdoutReader (Jdb jdb) { super("jdb stdout reader"); this.jdb = jdb; this.in = jdb.getStdout(); if (in == null) { throw new Failure("Can not get jdb stdout stream"); } this.setDaemon(true); } public String toString() { return getClass().getName() + '@' + Integer.toHexString(hashCode()); } public void run() { synchronized(jdb.startNotify) { jdb.startNotify.notifyAll(); } long delta = 10; // time in milliseconds to wait at every iteration. boolean jdbWasTerminated = false; while (!stop) { if(jdb.terminated()) jdbWasTerminated = true; try { int size = in.available(); if (size > 0) { byte[] buffer = new byte [size]; int result = in.read(buffer, 0, size); if (result < 0) { throw new Failure("No bytes read from jdb's output stream "); } else if (result < size) { throw new Failure("Number bytes read from jdb's output stream are less than available " + "\n\t available : " + size + ", read : " + result); } logToFile(new String(buffer, 0, result)); } } catch (Exception e) { e.printStackTrace(jdb.getLauncher().getLog().getOutStream()); throw new Failure("Caught unexpected exception while reading jdb's stdout stream: " + e); } if(jdbWasTerminated) break; try { sleep(delta); } catch (InterruptedException ie) { ie.printStackTrace(jdb.getLauncher().getLog().getOutStream()); throw new Failure("Caught interrupted exception while waiting for jdb reply:\n\t" + ie); } } } public void close() { stop = true; try { if (in != null) { in.close(); } } catch (IOException ioe) { ioe.printStackTrace(jdb.getLauncher().getLog().getOutStream()); throw new Failure("Caught unexpected IOException while closing jdb stdout stream: " + ioe); } } } /** Handler for jdb stderr stream. */ class JdbStderrReader extends Thread { private Jdb jdb = null; private volatile boolean cancelled = false; private boolean empty = true; private BufferedReader bin; private PrintStream fout; private String fileName; JdbStderrReader (Jdb jdb, String jdbStderrFile) { super("jdb stderr reader"); this.jdb = jdb; InputStream in = jdb.getStderr(); if (in == null) { throw new Failure("Can not get jdb stderr stream"); } this.bin = new BufferedReader(new InputStreamReader(in)); this.setDaemon(true); this.fileName = jdbStderrFile; launcher.getLog().display("Creating file for jdb stderr stream: " + fileName); try { this.fout = new PrintStream(new BufferedOutputStream(new FileOutputStream(fileName))); } catch (Exception e) { e.printStackTrace(jdb.getLauncher().getLog().getOutStream()); throw new Failure("Caught unexpected exception while creating file for jdb stderr stream: " + e); } } public void run () { synchronized(jdb.startNotify) { jdb.startNotify.notifyAll(); } long delta = 10; // time in milliseconds to wait at every iteration. while (!cancelled) { String line = null; try { line = bin.readLine(); if (line == null) break; //EOF } catch (IOException ioe) { ioe.printStackTrace(jdb.getLauncher().getLog().getOutStream()); throw new Failure("Caught unexpected IOException while reading from jdb stderr: " + ioe); } if (line != null) { empty = false; logToFile(line); } try { sleep(delta); } catch (InterruptedException ie) { throw new Failure("Caught interrupted exception while waiting for jdb reply:\n\t" + ie); } } close(); } /** * Signal to run() method that it should terminate, * and wait until it is finished. */ public void cancel () { cancelled = true; while (this.isAlive()) { try { this.join(); } catch (InterruptedException ie) { close(); throw new Failure("Caught InterruptedException while waiting for JdbStderrReader termination " + ie); } } close(); } public void close() { if (fout != null) { synchronized (fout) { fout.close(); } } try { if (bin != null) { bin.close(); } } catch (IOException ioe) { ioe.printStackTrace(jdb.getLauncher().getLog().getOutStream()); throw new Failure("Caught unexpected IOException while closing jdb stderr stream: " + ioe); } if (!empty) { // Should not throw exception here because of non-empty stderr in case of unsuccessful launch of attaching jdb. jdb.getLauncher().getLog().display("JdbStderrReader: jdb's stderr is not empty. Check jdb.stderr file"); } } public String getFileName () { return this.fileName; } public void logToFile(String line) { synchronized (fout) { fout.println(line); fout.flush(); } } } // end of JdbStderrReader } // end of Jdb