/* * Copyright (c) 2001, 2021, 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.jdi; import jdk.test.lib.Platform; import nsk.share.*; import nsk.share.jpda.*; import com.sun.jdi.*; import com.sun.jdi.connect.*; import com.sun.jdi.connect.Connector.Argument; import java.io.*; import java.net.*; import java.util.*; /** * This class provides debugger with connection to debugee VM * using JDI connectors. *
* This class provides abilities to launch and bind to debugee VM
* as described for base DebugeeBinder
class,
* using JDI connectors and com.sun.VirtualMachine
mirror.
*
* When Binder
is asked to bind to debugee by invoking
* bindToBebugee()
method it uses
* com.sun.jdi.Connector
object corresponding to
* value of command line options -connector
and
* -transport
to launch and connect to debugee VM.
* After debugee is launched and connection is established
* Binder
uses com.sun.jdi.VirtualMachine
* object to construct Debugee
object, that
* provides abilities to interact with debugee VM.
*
* @see Debugee
* @see DebugeeBinder
*/
public class Binder extends DebugeeBinder {
/**
* Default message prefix for Binder
object.
*/
public static final String LOG_PREFIX = "binder> ";
/**
* Get version string.
*/
public static String getVersion () {
return "@(#)Binder.java 1.14 03/10/08";
}
// -------------------------------------------------- //
/**
* Handler of command line arguments.
*/
private ArgumentHandler argumentHandler = null;
/**
* Return argumentHandler
of this binder.
*/
public ArgumentHandler getArgumentHandler() {
return argumentHandler;
}
// -------------------------------------------------- //
/**
* Make Binder
object and pass raw command line arguments.
*
* @deprecated Use newer
* Binder(ArgumentHandler,Log)
* constructor.
*/
@Deprecated
public Binder (String args[]) {
this(args, new Log(System.err));
}
/**
* Make Binder
object for raw command line arguments
* and specified log
object.
*
* @deprecated Use newer
* Binder(ArgumentHandler,Log)
* constructor.
*/
@Deprecated
public Binder (String args[], Log log) {
this(new ArgumentHandler(args), log);
}
/**
* Make Binder
object for specified command line arguments
* and log
object.
*/
public Binder (ArgumentHandler argumentHandler, Log log) {
super(argumentHandler, log);
this.argumentHandler = argumentHandler;
}
// -------------------------------------------------- //
/**
* Make initial Debugee
object for local debuggee process
* started with launching connector.
*/
public Debugee makeLocalDebugee(Process process) {
LocalLaunchedDebugee debugee = new LocalLaunchedDebugee(process, this);
Finalizer finalizer = new Finalizer(debugee);
finalizer.activate();
return debugee;
}
/**
* Launch local debuggee process with specified command line
* and make initial Debugee
object.
*/
public Debugee startLocalDebugee(String cmd) {
Process process = null;
try {
process = launchProcess(cmd);
} catch (IOException e) {
e.printStackTrace(log.getOutStream());
throw new Failure("Caught exception while launching local debuggee VM process:\n\t"
+ e);
}
return makeLocalDebugee(process);
}
/**
* Make debuggee wrapper for already launched debuggee VM.
* After enwraping debugee's output is redirected to Binder's log,
* VMStartEvent is received and debuggee is initialized.
*/
public Debugee enwrapDebugee(VirtualMachine vm, Process proc) {
Debugee debugee = makeLocalDebugee(proc);
display("Redirecting VM output");
debugee.redirectOutput(log);
debugee.setupVM(vm);
long timeout = argumentHandler.getWaitTime() * 60 * 1000; // milliseconds
display("Waiting for VM initialized");
debugee.waitForVMInit(timeout);
return debugee;
}
/**
* Launch debugee VM and establish connection to it without waiting for VMStartEvent.
* After launching debugee's output is redirected to Binder's log,
* but VMStartEvent is not received and so debuggee is not fully initialized.
*
* @see #bindToDebugee(String)
*/
public Debugee bindToDebugeeNoWait(String classToExecute) {
VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
display("VirtualMachineManager: version "
+ vmm.majorInterfaceVersion() + "."
+ vmm.minorInterfaceVersion());
Debugee debugee = null;
String classPath = null;
// classPath = System.getProperty("java.class.path");
prepareForPipeConnection(argumentHandler);
if (argumentHandler.isLaunchedLocally()) {
if (argumentHandler.isDefaultConnector()) {
debugee = localDefaultLaunchDebugee(vmm, classToExecute, classPath);
} else if (argumentHandler.isRawLaunchingConnector()) {
debugee = localRawLaunchDebugee(vmm, classToExecute, classPath);
} else if (argumentHandler.isLaunchingConnector()) {
debugee = localLaunchDebugee(vmm, classToExecute, classPath);
} else if (argumentHandler.isAttachingConnector()) {
debugee = localLaunchAndAttachDebugee(vmm, classToExecute, classPath);
} else if (argumentHandler.isListeningConnector()) {
debugee = localLaunchAndListenDebugee(vmm, classToExecute, classPath);
} else {
throw new TestBug("Unexpected connector type for local debugee launch mode"
+ argumentHandler.getConnectorType());
}
} else if (argumentHandler.isLaunchedRemotely()) {
connectToBindServer(classToExecute);
if (argumentHandler.isAttachingConnector()) {
debugee = remoteLaunchAndAttachDebugee(vmm, classToExecute, classPath);
} else if (argumentHandler.isListeningConnector()) {
debugee = remoteLaunchAndListenDebugee(vmm, classToExecute, classPath);
} else {
throw new TestBug("Unexpected connector type for remote debugee launch mode"
+ argumentHandler.getConnectorType());
}
} else if (argumentHandler.isLaunchedManually()) {
if (argumentHandler.isAttachingConnector()) {
debugee = manualLaunchAndAttachDebugee(vmm, classToExecute, classPath);
} else if (argumentHandler.isListeningConnector()) {
debugee = manualLaunchAndListenDebugee(vmm, classToExecute, classPath);
} else {
throw new TestBug("Unexpected connector type for manual debugee launch mode"
+ argumentHandler.getConnectorType());
}
} else {
throw new Failure("Unexpected debugee launching mode: " + argumentHandler.getLaunchMode());
}
return debugee;
}
/**
* Launch debugee VM and establish JDI connection.
* After launching debugee's output is redirected to Binder's log,
* VMStart event is received and debuggee is initialized.
*
* @see #bindToDebugeeNoWait(String)
*/
public Debugee bindToDebugee(String classToExecute) {
Debugee debugee = bindToDebugeeNoWait(classToExecute);
if(argumentHandler.getOptions().getProperty("traceAll") != null)
debugee.VM().setDebugTraceMode(VirtualMachine.TRACE_ALL);
long timeout = argumentHandler.getWaitTime() * 60 * 1000; // milliseconds
display("Waiting for VM initialized");
debugee.waitForVMInit(timeout);
return debugee;
}
// -------------------------------------------------- //
/**
* Launch debugee locally via the default LaunchingConnector.
*/
private Debugee localDefaultLaunchDebugee (VirtualMachineManager vmm,
String classToExecute,
String classPath) {
display("Finding connector: " + "default" );
LaunchingConnector connector = vmm.defaultConnector();
MapAttachingConnector
.
*/
private Debugee localLaunchAndAttachDebugee (VirtualMachineManager vmm,
String classToExecute,
String classPath) {
display("FindingConnector: " + argumentHandler.getConnectorName() );
AttachingConnector connector =
(AttachingConnector) findConnector(argumentHandler.getConnectorName(),
vmm.attachingConnectors());
MapListeningConnector
.
*/
private Debugee localLaunchAndListenDebugee (VirtualMachineManager vmm,
String classToExecute,
String classPath) {
display("Finding connector: " + argumentHandler.getConnectorName() );
ListeningConnector connector =
(ListeningConnector) findConnector(argumentHandler.getConnectorName(),
vmm.listeningConnectors());
MapBindServer
and connect to it using
* AttachingConnector
.
*/
private Debugee remoteLaunchAndAttachDebugee (VirtualMachineManager vmm,
String classToExecute,
String classPath) {
display("Finding connector: " + argumentHandler.getConnectorName() );
AttachingConnector connector =
(AttachingConnector) findConnector(argumentHandler.getConnectorName(),
vmm.attachingConnectors());
MapBindServer
and connect to it using
* ListeningConnector
.
*/
private Debugee remoteLaunchAndListenDebugee (VirtualMachineManager vmm,
String classToExecute,
String classPath) {
display("Finding connector: " + argumentHandler.getConnectorName() );
ListeningConnector connector =
(ListeningConnector) findConnector(argumentHandler.getConnectorName(),
vmm.listeningConnectors());
MapAttachingConnector
.
*/
private Debugee manualLaunchAndAttachDebugee (VirtualMachineManager vmm,
String classToExecute,
String classPath) {
display("Finding connector: " + argumentHandler.getConnectorName() );
AttachingConnector connector =
(AttachingConnector) findConnector(argumentHandler.getConnectorName(),
vmm.attachingConnectors());
MapListeningConnector
.
*/
private Debugee manualLaunchAndListenDebugee (VirtualMachineManager vmm,
String classToExecute,
String classPath) {
display("Finding connector: " + argumentHandler.getConnectorName() );
ListeningConnector connector =
(ListeningConnector) findConnector(argumentHandler.getConnectorName(),
vmm.listeningConnectors());
MapDebugee
mirror.
*/
protected Debugee startLocalDebugee(String[] cmdArgs) {
Process process = null;
try {
process = launchProcess(cmdArgs);
} catch (IOException e) {
e.printStackTrace(log.getOutStream());
throw new Failure("Caught exception while launching local debuggee VM process:\n\t"
+ e);
}
return makeLocalDebugee(process);
}
/**
* Launch remote debuggee process with specified command line arguments
* and make initial Debugee
mirror.
*/
protected RemoteLaunchedDebugee startRemoteDebugee(String[] cmdArgs) {
try {
launchRemoteProcess(cmdArgs);
} catch (IOException e) {
e.printStackTrace(log.getOutStream());
throw new Failure("Caught exception while launching remote debuggee VM process:\n\t"
+ e);
}
RemoteLaunchedDebugee debugee = new RemoteLaunchedDebugee(this);
Finalizer finalizer = new Finalizer(debugee);
finalizer.activate();
return debugee;
}
/**
* Launch manual debuggee process with specified command line arguments
* and make initial Debugee
mirror.
*/
protected ManualLaunchedDebugee startManualDebugee(String cmd) {
ManualLaunchedDebugee debugee = new ManualLaunchedDebugee(this);
debugee.launchDebugee(cmd);
Finalizer finalizer = new Finalizer(debugee);
finalizer.activate();
return debugee;
}
public static String readVMStartExceptionOutput(VMStartException e, PrintStream log) {
StringBuffer msg = new StringBuffer();
try (InputStream is = e.process().getInputStream()) {
msg.append("\tstdout: ").append(new String(readAllBytes(is))).append('\n');
} catch (IOException e1) {
log.println("Could not read normal output from launched process:" + e1);
}
try (InputStream is = e.process().getErrorStream()) {
msg.append("\tstderr: ").append(new String(readAllBytes(is)));
} catch (IOException e1) {
log.println("Could not read error output from launched process:" + e1);
}
return msg.toString();
}
/**
* Copied from the JDK 9 implementation in InputStream.java
*/
private static byte[] readAllBytes(InputStream is) throws IOException {
final int DEFAULT_BUFFER_SIZE = 8192;
final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
int capacity = buf.length;
int nread = 0;
int n;
for (;;) {
// read to EOF which may read more or less than initial buffer size
while ((n = is.read(buf, nread, capacity - nread)) > 0)
nread += n;
// if the last call to read returned -1, then we're done
if (n < 0)
break;
// need to allocate a larger buffer
if (capacity <= MAX_BUFFER_SIZE - capacity) {
capacity = capacity << 1;
} else {
if (capacity == MAX_BUFFER_SIZE)
throw new OutOfMemoryError("Required array size too large");
capacity = MAX_BUFFER_SIZE;
}
buf = Arrays.copyOf(buf, capacity);
}
return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
}
}
/**
* Mirror of locally launched debugee.
*/
final class LocalLaunchedDebugee extends Debugee {
/** Enwrap the locally started VM process. */
public LocalLaunchedDebugee (Process process, Binder binder) {
super(binder);
this.process = process;
checkTermination = true;
}
// ---------------------------------------------- //
/** Return exit status of the debugee VM. */
public int getStatus () {
return process.exitValue();
}
/** Check whether the debugee VM has been terminated. */
public boolean terminated () {
if (process == null)
return true;
try {
int value = process.exitValue();
return true;
} catch (IllegalThreadStateException e) {
return false;
}
}
// ---------------------------------------------- //
/** Kill the debugee VM. */
protected void killDebugee () {
super.killDebugee();
if (!terminated()) {
log.display("Killing debugee VM process");
process.destroy();
}
}
/** Wait until the debugee VM shutdown or crash. */
protected int waitForDebugee () throws InterruptedException {
int code = process.waitFor();
return code;
}
/** Get a pipe to write to the debugee's stdin stream. */
protected OutputStream getInPipe () {
return process.getOutputStream();
}
/** Get a pipe to read the debugee's stdout stream. */
protected InputStream getOutPipe () {
return process.getInputStream();
}
/** Get a pipe to read the debugee's stderr stream. */
protected InputStream getErrPipe () {
return process.getErrorStream();
}
}
/**
* Mirror of remotely launched debugee.
*/
final class RemoteLaunchedDebugee extends Debugee {
/** Enwrap the remotely started VM process. */
public RemoteLaunchedDebugee (Binder binder) {
super(binder);
}
// ---------------------------------------------- //
/** Return exit status of the debugee VM. */
public int getStatus () {
return binder.getRemoteProcessStatus();
}
/** Check whether the debugee VM has been terminated. */
public boolean terminated () {
return binder.isRemoteProcessTerminated();
}
// ---------------------------------------------- //
/** Kill the debugee VM. */
protected void killDebugee () {
super.killDebugee();
if (!terminated()) {
binder.killRemoteProcess();
}
}
/** Wait until the debugee VM shutdown or crash. */
protected int waitForDebugee () {
return binder.waitForRemoteProcess();
}
/** Get a pipe to write to the debugee's stdin stream. */
protected OutputStream getInPipe () {
return null;
}
/** Get a pipe to read the debugee's stdout stream. */
protected InputStream getOutPipe () {
return null;
}
/** Get a pipe to read the debugee's stderr stream. */
protected InputStream getErrPipe () {
return null;
}
public void redirectStdout(OutputStream out) {
}
public void redirectStdout(Log log, String prefix) {
}
public void redirectStderr(OutputStream out) {
}
public void redirectStderr(Log log, String prefix) {
}
}
/**
* Mirror of manually launched debugee.
*/
final class ManualLaunchedDebugee extends Debugee {
/** Enwrap the manually started VM process. */
public ManualLaunchedDebugee (Binder binder) {
super(binder);
makeInputReader();
}
// ---------------------------------------------- //
private int exitCode = 0;
private boolean finished = false;
private static BufferedReader bin = null;
public void launchDebugee(String commandLine) {
makeInputReader();
putMessage("Launch target VM using such command line:\n"
+ commandLine);
String answer = askQuestion("Has the VM successfully started? (yes/no)", "yes");
for ( ; ; ) {
if (answer.equals("yes"))
break;
if (answer.equals("no"))
throw new Failure ("Unable to manually launch debugee VM");
answer = askQuestion("Wrong answer. Please type yes or no", "yes");
}
}
private static void makeInputReader() {
if (bin == null) {
bin = new BufferedReader(new InputStreamReader(System.in));
}
}
private static void destroyInputReader() {
if (bin != null) {
try {
bin.close();
} catch (IOException e) {
// e.printStackTrace(log.getOutStream());
throw new Failure("Caught exception while closing input stream:\n\t" + e);
}
bin = null;
}
}
private static void putMessage(String msg) {
System.out.println("\n>>> " + msg);
}
private static String askQuestion(String question, String defaultAnswer) {
try {
System.out.print("\n>>> " + question);
System.out.print(" [" + defaultAnswer + "] ");
System.out.flush();
String answer = bin.readLine();
if (answer.equals(""))
return defaultAnswer;
return answer;
} catch (IOException e) {
// e.printStackTrace(log.getOutStream());
throw new Failure("Caught exception while reading answer:\n\t" + e);
}
}
/** Return exit status of the debugee VM. */
public int getStatus () {
if (! finished) {
throw new Failure("Unable to get status of debugee VM: process still alive");
}
return exitCode;
}
/** Check whether the debugee VM has been terminated. */
public boolean terminated () {
return finished;
}
// ---------------------------------------------- //
/** Kill the debugee VM. */
protected void killDebugee () {
super.killDebugee();
if (!terminated()) {
putMessage("Kill launched VM");
String answer = askQuestion("Has the VM successfully terminated? (yes/no)", "yes");
for ( ; ; ) {
if (answer.equals("yes")) {
finished = true;
break;
}
if (answer.equals("no"))
throw new Failure ("Unable to manually kill debugee VM");
answer = askQuestion("Wrong answer. Please type yes or no", "yes");
}
}
}
/** Wait until the debugee VM shutdown or crash. */
protected int waitForDebugee () {
putMessage("Wait for launched VM to exit.");
String answer = askQuestion("What is VM exit code?", "95");
for ( ; ; ) {
try {
exitCode = Integer.parseInt(answer);
break;
} catch (NumberFormatException e) {
answer = askQuestion("Wrong answer. Please type integer value", "95");
}
}
finished = true;
return exitCode;
}
/** Get a pipe to write to the debugee's stdin stream. */
protected OutputStream getInPipe () {
return null;
}
/** Get a pipe to read the debugee's stdout stream. */
protected InputStream getOutPipe () {
return null;
}
/** Get a pipe to read the debugee's stderr stream. */
protected InputStream getErrPipe () {
return null;
}
public void redirectStdout(OutputStream out) {
}
public void redirectStdout(Log log, String prefix) {
}
public void redirectStderr(OutputStream out) {
}
public void redirectStderr(Log log, String prefix) {
}
public void close() {
destroyInputReader();
super.close();
}
}