blob: 423d12514200fe4d4e0ab48e6086a8bf1872375d [file] [log] [blame]
/*
* Copyright (c) 2002, 2018, 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 <i>jdb</i>.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 <i>stdout</i> stream */
static final String JDB_STDOUT_FILE = "jdb.stdout";
/** File to log <i>stderr</i> 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 <i>jdb</i> session composed from commands and <i>jdb</i> 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;
/** <i>Launcher</i> that creates this <i>Jdb</i> object. */
private static Launcher launcher = null;
/** Internal buffer to save all not-null string from <i>jdb</i> stdout */
volatile private static StringBuffer stdoutBuffer = new StringBuffer();
volatile private Object startNotify = new Object();
/** Returns <i>Launcher</i> that created this <i>Jdb</i> 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 <i>Jdb</i> 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 <i>jdb</i> with options defined in <i>launchCmdArgs</i>.
* Full path to <i>jdb</i> must be defined as first element in <i>launchCmdArgs</i>.
*/
public void launch(String[] launchCmdArgs) throws IOException {
super.launch(launchCmdArgs);
redirectStreams();
}
/**
* Launch <i>jdb</i> with options defined in <i>launchCmdLine</i>.
* Full path to <i>jdb</i> must be defined as first token in <i>launchCmdLine</i>.
*/
public void launch(String launchCmdLine) throws IOException {
super.launch(launchCmdLine);
redirectStreams();
}
/**
* Gets <i>stdin, stdout, stderr</i> streams of the <i>jdb's</i> 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 <i>jdb's</i> 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 <i>jdb's</i> 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 <code>LocalProcess.PROCESS_IS_ALIVE</code> 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 <i>jdbCommand</i> to <i>jdb's</i> 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() - 1);
}
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 <i>jdb's</i> input stream, waits for compound promt received,
* and then returns reply from <i>jdb's</i> 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 <i>jdb's</i> input stream, waits for promt received,
* and then returns reply from <i>jdb's</i> output stream.
*
* @param command string representing full command with all arguments if any.
* @param compoundPromptOnly read <i>output</i> until compound prompt is found.
*/
public String[] receiveReplyFor(String command, boolean compoundPromptOnly) {
return receiveReplyFor(command, compoundPromptOnly, 1);
}
/**
* Sends command to <i>jdb's</i> input stream, waits for given number of promts received,
* and then returns reply from <i>jdb's</i> output stream.
*
* @param command string representing full command with all arguments if any.
* @param compoundPromptOnly read <i>output</i> 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 <i>jdb's</i> input stream, waits for specified message to be received,
* and then returns reply from <i>jdb's</i> 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 <i>jdb</i> stdout
* beginning from <i>startPos</i> in the <i>stdoutBuffer</i>.
*
* @param startPos start position for search in <i>stdoutBuffer</i>.
*/
public String[] receiveReply(int startPos) {
return receiveReply(startPos, true);
}
/**
* Waits for particular prompt and returns reply from <i>jdb</i> stdout
* beginning from <i>startPos</i> in the <i>stdoutBuffer</i>.
*
* @param startPos start position for search in <i>stdoutBuffer</i>.
* @param compoundPromptOnly waits for compound prompt only.
*/
public String[] receiveReply(int startPos, boolean compoundPromptOnly) {
return receiveReply(startPos, compoundPromptOnly, 1);
}
/**
* Waits for <i>count</i> number of prompts and returns reply from <i>jdb</i> stdout
* beginning from <i>startPos</i> in the <i>stdoutBuffer</i>.
*
* @param startPos start position for search in <i>stdoutBuffer</i>.
* @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) {
int found = waitForPrompt(startPos, compoundPromptOnly, count);
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]);
}
return replyArr;
}
/**
* Reads <i>JDB_STDOUT_FILE</i> file until prompt is found in the <i>stdoutBuffer</i>.
*
* @param startPos start position for search in <i>stdoutBuffer</i>.
* @param compoundPromptOnly search for compound prompt only.
* @throws Failure if prompt is not encountered during <i>WaitTime</i>.
* @return number of prompt instances really found.
*/
public int waitForPrompt(int startPos, boolean compoundPromptOnly) {
return waitForPrompt(startPos, compoundPromptOnly, 1);
}
/**
* Reads <i>JDB_STDOUT_FILE</i> file until prompt is found in the <i>stdoutBuffer</i>
* <i>count</i> times.
*
* @param startPos start position for search in <i>stdoutBuffer</i>.
* @param compoundPromptOnly search for compound prompt only.
* @throws Failure if prompt is not encountered <i>count</i> times during <i>WaitTime</i>.
* @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 promts 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: (?<DebuggeeException>\\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 <i>JDB_STDOUT_FILE</i> file until expected message is found in the <i>stdoutBuffer</i>.
*
* @param startPos start position for search in <i>stdoutBuffer</i>.
* @throws Failure if expected message is not encountered during <i>WaitTime</i>.
* @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 <i>JDB_STDOUT_FILE</i> file starting from <i>startPos</i>.
*
* @param startPos start position for search in <i>stdoutBuffer</i>.
* @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 <i>jdb</i> prompt of particular kind.
* starting from <code>startPos</code>.
* 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)).
* <p>
* 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<String> v = new Vector<String>();
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 + 1;
} 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 <run> 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<String> v = new Vector<String>();
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 <i>threadName</i>.
*/
public String[] getThreadIds(String threadName) {
if (!threadName.startsWith("(")) {
threadName = "(" + threadName;
}
if (!threadName.endsWith(")")) {
threadName = threadName + ")";
}
Vector<String> v = new Vector<String>();
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];
int j = string.indexOf(threadName);
if (j >= 0) {
j += threadName.length();
String threadId = string.substring(j, string.indexOf(" ", j));
v.add(threadId);
}
}
String[] result = new String [v.size()];
v.toArray(result);
return result;
}
/**
* Quit <i>jdb</i> 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 + " <cont> commands");
}
}
}
/**
* Returns string array containing all strings from <i>jdb</i> stdout.
*/
public String[] getTotalReply() {
return toStringArray(stdoutBuffer.toString());
}
/**
* Prints given message to log files and adds to <i>stdoutBuffer</i>.
*/
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 <i>waitTime</i>
* 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 <i>jdb</i> 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 <i>run()</i> 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