|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
/* |
|
* This source code is provided to illustrate the usage of a given feature |
|
* or technique and has been deliberately simplified. Additional steps |
|
* required for a production-quality application, such as security checks, |
|
* input validation and proper error handling, might not be present in |
|
* this sample code. |
|
*/ |
|
|
|
|
|
package com.sun.tools.example.debug.tty; |
|
|
|
import com.sun.jdi.*; |
|
import com.sun.jdi.connect.*; |
|
import com.sun.jdi.request.EventRequestManager; |
|
import com.sun.jdi.request.ThreadStartRequest; |
|
import com.sun.jdi.request.ThreadDeathRequest; |
|
|
|
import java.util.*; |
|
import java.util.regex.*; |
|
import java.io.*; |
|
|
|
class VMConnection { |
|
|
|
private VirtualMachine vm; |
|
private Process process = null; |
|
private int outputCompleteCount = 0; |
|
|
|
private final Connector connector; |
|
private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs; |
|
private final int traceFlags; |
|
|
|
synchronized void notifyOutputComplete() { |
|
outputCompleteCount++; |
|
notifyAll(); |
|
} |
|
|
|
synchronized void waitOutputComplete() { |
|
|
|
if (process != null) { |
|
while (outputCompleteCount < 2) { |
|
try {wait();} catch (InterruptedException e) {} |
|
} |
|
} |
|
} |
|
|
|
private Connector findConnector(String name) { |
|
for (Connector connector : |
|
Bootstrap.virtualMachineManager().allConnectors()) { |
|
if (connector.name().equals(name)) { |
|
return connector; |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
private Map <String, com.sun.jdi.connect.Connector.Argument> parseConnectorArgs(Connector connector, String argString) { |
|
Map<String, com.sun.jdi.connect.Connector.Argument> arguments = connector.defaultArguments(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
String regexPattern = |
|
"(quote=[^,]+,)|" + |
|
"(\\w+=)" + |
|
"(((\"[^\"]*\")|" + |
|
"('[^']*')|" + |
|
"([^,'\"]+))+,)"; |
|
Pattern p = Pattern.compile(regexPattern); |
|
Matcher m = p.matcher(argString); |
|
while (m.find()) { |
|
int startPosition = m.start(); |
|
int endPosition = m.end(); |
|
if (startPosition > 0) { |
|
|
|
|
|
*/ |
|
throw new IllegalArgumentException |
|
(MessageOutput.format("Illegal connector argument", |
|
argString)); |
|
} |
|
|
|
String token = argString.substring(startPosition, endPosition); |
|
int index = token.indexOf('='); |
|
String name = token.substring(0, index); |
|
String value = token.substring(index + 1, |
|
token.length() - 1); |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
if (name.equals("options")) { |
|
StringBuilder sb = new StringBuilder(); |
|
for (String s : splitStringAtNonEnclosedWhiteSpace(value)) { |
|
while (isEnclosed(s, "\"") || isEnclosed(s, "'")) { |
|
s = s.substring(1, s.length() - 1); |
|
} |
|
sb.append(s); |
|
sb.append(" "); |
|
} |
|
value = sb.toString(); |
|
} |
|
|
|
Connector.Argument argument = arguments.get(name); |
|
if (argument == null) { |
|
throw new IllegalArgumentException |
|
(MessageOutput.format("Argument is not defined for connector:", |
|
new Object [] {name, connector.name()})); |
|
} |
|
argument.setValue(value); |
|
|
|
argString = argString.substring(endPosition); |
|
m = p.matcher(argString); |
|
} |
|
if ((! argString.equals(",")) && (argString.length() > 0)) { |
|
|
|
|
|
|
|
*/ |
|
throw new IllegalArgumentException |
|
(MessageOutput.format("Illegal connector argument", argString)); |
|
} |
|
return arguments; |
|
} |
|
|
|
private static boolean isEnclosed(String value, String enclosingChar) { |
|
if (value.indexOf(enclosingChar) == 0) { |
|
int lastIndex = value.lastIndexOf(enclosingChar); |
|
if (lastIndex > 0 && lastIndex == value.length() - 1) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
private static List<String> splitStringAtNonEnclosedWhiteSpace(String value) throws IllegalArgumentException { |
|
List<String> al = new ArrayList<String>(); |
|
char[] arr; |
|
int startPosition = 0; |
|
int endPosition = 0; |
|
final char SPACE = ' '; |
|
final char DOUBLEQ = '"'; |
|
final char SINGLEQ = '\''; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
char enclosingTargetChar = SPACE; |
|
|
|
if (value == null) { |
|
throw new IllegalArgumentException |
|
(MessageOutput.format("value string is null")); |
|
} |
|
|
|
|
|
arr = value.toCharArray(); |
|
|
|
for (int i = 0; i < arr.length; i++) { |
|
switch (arr[i]) { |
|
case SPACE: { |
|
// do nothing for spaces |
|
|
|
if (isLastChar(arr, i)) { |
|
endPosition = i; |
|
|
|
break; |
|
} |
|
continue; |
|
} |
|
case DOUBLEQ: |
|
case SINGLEQ: { |
|
if (enclosingTargetChar == arr[i]) { |
|
|
|
if (isNextCharWhitespace(arr, i)) { |
|
// if peek next is whitespace |
|
|
|
endPosition = i; |
|
|
|
enclosingTargetChar = SPACE; |
|
|
|
break; |
|
} |
|
} |
|
if (enclosingTargetChar == SPACE) { |
|
// no open enclosing state |
|
|
|
if (isPreviousCharWhitespace(arr, i)) { |
|
startPosition = i; |
|
|
|
if (value.indexOf(arr[i], i + 1) >= 0) { |
|
// set open enclosing state by |
|
|
|
enclosingTargetChar = arr[i]; |
|
} else { |
|
// no more target chars left to match |
|
|
|
if (isNextCharWhitespace(arr, i)) { |
|
endPosition = i; |
|
|
|
break; |
|
} |
|
} |
|
} |
|
} |
|
continue; |
|
} |
|
default: { |
|
|
|
if (enclosingTargetChar == SPACE) { |
|
|
|
if (isPreviousCharWhitespace(arr, i)) { |
|
|
|
startPosition = i; |
|
} |
|
if (isNextCharWhitespace(arr, i)) { |
|
|
|
endPosition = i; |
|
|
|
break; |
|
} |
|
} |
|
continue; |
|
} |
|
} |
|
|
|
|
|
if (startPosition > endPosition) { |
|
throw new IllegalArgumentException |
|
(MessageOutput.format("Illegal option values")); |
|
} |
|
|
|
|
|
al.add(value.substring(startPosition, ++endPosition)); |
|
|
|
|
|
i = startPosition = endPosition; |
|
|
|
} // for loop |
|
|
|
return al; |
|
} |
|
|
|
static private boolean isPreviousCharWhitespace(char[] arr, int curr_pos) { |
|
return isCharWhitespace(arr, curr_pos - 1); |
|
} |
|
|
|
static private boolean isNextCharWhitespace(char[] arr, int curr_pos) { |
|
return isCharWhitespace(arr, curr_pos + 1); |
|
} |
|
|
|
static private boolean isCharWhitespace(char[] arr, int pos) { |
|
if (pos < 0 || pos >= arr.length) { |
|
|
|
return true; |
|
} |
|
if (arr[pos] == ' ') { |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
static private boolean isLastChar(char[] arr, int pos) { |
|
return (pos + 1 == arr.length); |
|
} |
|
|
|
VMConnection(String connectSpec, int traceFlags) { |
|
String nameString; |
|
String argString; |
|
int index = connectSpec.indexOf(':'); |
|
if (index == -1) { |
|
nameString = connectSpec; |
|
argString = ""; |
|
} else { |
|
nameString = connectSpec.substring(0, index); |
|
argString = connectSpec.substring(index + 1); |
|
} |
|
|
|
connector = findConnector(nameString); |
|
if (connector == null) { |
|
throw new IllegalArgumentException |
|
(MessageOutput.format("No connector named:", nameString)); |
|
} |
|
|
|
connectorArgs = parseConnectorArgs(connector, argString); |
|
this.traceFlags = traceFlags; |
|
} |
|
|
|
synchronized VirtualMachine open() { |
|
if (connector instanceof LaunchingConnector) { |
|
vm = launchTarget(); |
|
} else if (connector instanceof AttachingConnector) { |
|
vm = attachTarget(); |
|
} else if (connector instanceof ListeningConnector) { |
|
vm = listenTarget(); |
|
} else { |
|
throw new InternalError |
|
(MessageOutput.format("Invalid connect type")); |
|
} |
|
vm.setDebugTraceMode(traceFlags); |
|
if (vm.canBeModified()){ |
|
setEventRequests(vm); |
|
resolveEventRequests(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
if (Env.getSourcePath().length() == 0) { |
|
if (vm instanceof PathSearchingVirtualMachine) { |
|
PathSearchingVirtualMachine psvm = |
|
(PathSearchingVirtualMachine) vm; |
|
Env.setSourcePath(psvm.classPath()); |
|
} else { |
|
Env.setSourcePath("."); |
|
} |
|
} |
|
|
|
return vm; |
|
} |
|
|
|
boolean setConnectorArg(String name, String value) { |
|
|
|
|
|
*/ |
|
if (vm != null) { |
|
return false; |
|
} |
|
|
|
Connector.Argument argument = connectorArgs.get(name); |
|
if (argument == null) { |
|
return false; |
|
} |
|
argument.setValue(value); |
|
return true; |
|
} |
|
|
|
String connectorArg(String name) { |
|
Connector.Argument argument = connectorArgs.get(name); |
|
if (argument == null) { |
|
return ""; |
|
} |
|
return argument.value(); |
|
} |
|
|
|
public synchronized VirtualMachine vm() { |
|
if (vm == null) { |
|
throw new VMNotConnectedException(); |
|
} else { |
|
return vm; |
|
} |
|
} |
|
|
|
boolean isOpen() { |
|
return (vm != null); |
|
} |
|
|
|
boolean isLaunch() { |
|
return (connector instanceof LaunchingConnector); |
|
} |
|
|
|
public void disposeVM() { |
|
try { |
|
if (vm != null) { |
|
vm.dispose(); |
|
vm = null; |
|
} |
|
} finally { |
|
if (process != null) { |
|
process.destroy(); |
|
process = null; |
|
} |
|
waitOutputComplete(); |
|
} |
|
} |
|
|
|
private void setEventRequests(VirtualMachine vm) { |
|
EventRequestManager erm = vm.eventRequestManager(); |
|
|
|
// Normally, we want all uncaught exceptions. We request them |
|
// via the same mechanism as Commands.commandCatchException() |
|
// so the user can ignore them later if they are not |
|
// interested. |
|
// FIXME: this works but generates spurious messages on stdout |
|
// during startup: |
|
// Set uncaught java.lang.Throwable |
|
|
|
Commands evaluator = new Commands(); |
|
evaluator.commandCatchException |
|
(new StringTokenizer("uncaught java.lang.Throwable")); |
|
|
|
ThreadStartRequest tsr = erm.createThreadStartRequest(); |
|
tsr.enable(); |
|
ThreadDeathRequest tdr = erm.createThreadDeathRequest(); |
|
tdr.enable(); |
|
} |
|
|
|
private void resolveEventRequests() { |
|
Env.specList.resolveAll(); |
|
} |
|
|
|
private void dumpStream(InputStream stream) throws IOException { |
|
BufferedReader in = |
|
new BufferedReader(new InputStreamReader(stream)); |
|
int i; |
|
try { |
|
while ((i = in.read()) != -1) { |
|
MessageOutput.printDirect((char)i); |
|
// printDirect() |
|
} |
|
} catch (IOException ex) { |
|
String s = ex.getMessage(); |
|
if (!s.startsWith("Bad file number")) { |
|
throw ex; |
|
} |
|
// else we got a Bad file number IOException which just means |
|
// that the debuggee has gone away. We'll just treat it the |
|
// same as if we got an EOF. |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void displayRemoteOutput(final InputStream stream) { |
|
Thread thr = new Thread("output reader") { |
|
@Override |
|
public void run() { |
|
try { |
|
dumpStream(stream); |
|
} catch (IOException ex) { |
|
MessageOutput.fatalError("Failed reading output"); |
|
} finally { |
|
notifyOutputComplete(); |
|
} |
|
} |
|
}; |
|
thr.setPriority(Thread.MAX_PRIORITY-1); |
|
thr.start(); |
|
} |
|
|
|
private void dumpFailedLaunchInfo(Process process) { |
|
try { |
|
dumpStream(process.getErrorStream()); |
|
dumpStream(process.getInputStream()); |
|
} catch (IOException e) { |
|
MessageOutput.println("Unable to display process output:", |
|
e.getMessage()); |
|
} |
|
} |
|
|
|
|
|
private VirtualMachine launchTarget() { |
|
LaunchingConnector launcher = (LaunchingConnector)connector; |
|
try { |
|
VirtualMachine vm = launcher.launch(connectorArgs); |
|
process = vm.process(); |
|
displayRemoteOutput(process.getErrorStream()); |
|
displayRemoteOutput(process.getInputStream()); |
|
return vm; |
|
} catch (IOException ioe) { |
|
ioe.printStackTrace(); |
|
MessageOutput.fatalError("Unable to launch target VM."); |
|
} catch (IllegalConnectorArgumentsException icae) { |
|
icae.printStackTrace(); |
|
MessageOutput.fatalError("Internal debugger error."); |
|
} catch (VMStartException vmse) { |
|
MessageOutput.println("vmstartexception", vmse.getMessage()); |
|
MessageOutput.println(); |
|
dumpFailedLaunchInfo(vmse.process()); |
|
MessageOutput.fatalError("Target VM failed to initialize."); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
private VirtualMachine attachTarget() { |
|
AttachingConnector attacher = (AttachingConnector)connector; |
|
try { |
|
return attacher.attach(connectorArgs); |
|
} catch (IOException ioe) { |
|
ioe.printStackTrace(); |
|
MessageOutput.fatalError("Unable to attach to target VM."); |
|
} catch (IllegalConnectorArgumentsException icae) { |
|
icae.printStackTrace(); |
|
MessageOutput.fatalError("Internal debugger error."); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
private VirtualMachine listenTarget() { |
|
ListeningConnector listener = (ListeningConnector)connector; |
|
try { |
|
String retAddress = listener.startListening(connectorArgs); |
|
MessageOutput.println("Listening at address:", retAddress); |
|
vm = listener.accept(connectorArgs); |
|
listener.stopListening(connectorArgs); |
|
return vm; |
|
} catch (IOException ioe) { |
|
ioe.printStackTrace(); |
|
MessageOutput.fatalError("Unable to attach to target VM."); |
|
} catch (IllegalConnectorArgumentsException icae) { |
|
icae.printStackTrace(); |
|
MessageOutput.fatalError("Internal debugger error."); |
|
} |
|
return null; |
|
} |
|
} |