1 /* 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.jshell.execution; 26 27 import java.io.File; 28 import java.util.ArrayList; 29 import java.util.HashMap; 30 import java.util.List; 31 import java.util.Map; 32 import java.util.Map.Entry; 33 import com.sun.jdi.Bootstrap; 34 import com.sun.jdi.VirtualMachine; 35 import com.sun.jdi.connect.Connector; 36 import com.sun.jdi.connect.LaunchingConnector; 37 import com.sun.jdi.connect.ListeningConnector; 38 39 /** 40 * Sets up a JDI connection, providing the resulting JDI {@link VirtualMachine} 41 * and the {@link Process} the remote agent is running in. 42 */ 43 public class JDIInitiator { 44 45 private VirtualMachine vm; 46 private Process process = null; 47 private final Connector connector; 48 private final String remoteAgent; 49 private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs; 50 51 /** 52 * Start the remote agent and establish a JDI connection to it. 53 * 54 * @param port the socket port for (non-JDI) commands 55 * @param remoteVMOptions any user requested VM options 56 * @param remoteAgent full class name of remote agent to launch 57 * @param isLaunch does JDI do the launch? That is, LaunchingConnector, 58 * otherwise we start explicitly and use ListeningConnector 59 */ 60 public JDIInitiator(int port, List<String> remoteVMOptions, 61 String remoteAgent, boolean isLaunch) { 62 this.remoteAgent = remoteAgent; 63 String connectorName 64 = isLaunch 65 ? "com.sun.jdi.CommandLineLaunch" 66 : "com.sun.jdi.SocketListen"; 67 this.connector = findConnector(connectorName); 68 if (connector == null) { 69 throw new IllegalArgumentException("No connector named: " + connectorName); 70 } 71 Map<String, String> argumentName2Value 72 = isLaunch 73 ? launchArgs(port, String.join(" ", remoteVMOptions)) 74 : new HashMap<>(); 75 this.connectorArgs = mergeConnectorArgs(connector, argumentName2Value); 76 this.vm = isLaunch 77 ? launchTarget() 78 : listenTarget(port, remoteVMOptions); 79 80 } 81 82 /** 83 * Returns the resulting {@code VirtualMachine} instance. 84 * 85 * @return the virtual machine 86 */ 87 public VirtualMachine vm() { 88 return vm; 89 } 90 91 /** 92 * Returns the launched process. 93 * 94 * @return the remote agent process 95 */ 96 public Process process() { 97 return process; 98 } 99 100 /* launch child target vm */ 101 private VirtualMachine launchTarget() { 102 LaunchingConnector launcher = (LaunchingConnector) connector; 103 try { 104 VirtualMachine new_vm = launcher.launch(connectorArgs); 105 process = new_vm.process(); 106 return new_vm; 107 } catch (Exception ex) { 108 reportLaunchFail(ex, "launch"); 109 } 110 return null; 111 } 112 113 /** 114 * Directly launch the remote agent and connect JDI to it with a 115 * ListeningConnector. 116 */ 117 private VirtualMachine listenTarget(int port, List<String> remoteVMOptions) { 118 ListeningConnector listener = (ListeningConnector) connector; 119 try { 120 // Start listening, get the JDI connection address 121 String addr = listener.startListening(connectorArgs); 122 debug("Listening at address: " + addr); 123 124 // Launch the RemoteAgent requesting a connection on that address 125 String javaHome = System.getProperty("java.home"); 126 List<String> args = new ArrayList<>(); 127 args.add(javaHome == null 128 ? "java" 129 : javaHome + File.separator + "bin" + File.separator + "java"); 130 args.add("-agentlib:jdwp=transport=" + connector.transport().name() + 131 ",address=" + addr); 132 args.addAll(remoteVMOptions); 133 args.add(remoteAgent); 134 args.add("" + port); 135 ProcessBuilder pb = new ProcessBuilder(args); 136 process = pb.start(); 137 138 // Forward out, err, and in 139 // Accept the connection from the remote agent 140 vm = listener.accept(connectorArgs); 141 listener.stopListening(connectorArgs); 142 return vm; 143 } catch (Exception ex) { 144 reportLaunchFail(ex, "listen"); 145 } 146 return null; 147 } 148 149 private Connector findConnector(String name) { 150 for (Connector cntor 151 : Bootstrap.virtualMachineManager().allConnectors()) { 152 if (cntor.name().equals(name)) { 153 return cntor; 154 } 155 } 156 return null; 157 } 158 159 private Map<String, Connector.Argument> mergeConnectorArgs(Connector connector, Map<String, String> argumentName2Value) { 160 Map<String, Connector.Argument> arguments = connector.defaultArguments(); 161 162 for (Entry<String, String> argumentEntry : argumentName2Value.entrySet()) { 163 String name = argumentEntry.getKey(); 164 String value = argumentEntry.getValue(); 165 Connector.Argument argument = arguments.get(name); 166 167 if (argument == null) { 168 throw new IllegalArgumentException("Argument is not defined for connector:" + 169 name + " -- " + connector.name()); 170 } 171 172 argument.setValue(value); 173 } 174 175 return arguments; 176 } 177 178 /** 179 * The JShell specific Connector args for the LaunchingConnector. 180 * 181 * @param portthe socket port for (non-JDI) commands 182 * @param remoteVMOptions any user requested VM options 183 * @return the argument map 184 */ 185 private Map<String, String> launchArgs(int port, String remoteVMOptions) { 186 Map<String, String> argumentName2Value = new HashMap<>(); 187 argumentName2Value.put("main", remoteAgent + " " + port); 188 argumentName2Value.put("options", remoteVMOptions); 189 return argumentName2Value; 190 } 191 192 private void reportLaunchFail(Exception ex, String context) { 193 throw new InternalError("Failed remote " + context + ": " + connector + 194 " -- " + connectorArgs, ex); 195 } 196 197 /** 198 * Log debugging information. Arguments as for {@code printf}. 199 * 200 * @param format a format string as described in Format string syntax 201 * @param args arguments referenced by the format specifiers in the format 202 * string. 203 */ 204 private void debug(String format, Object... args) { 205 // Reserved for future logging 206 } 207 208 /** 209 * Log a serious unexpected internal exception. 210 * 211 * @param ex the exception 212 * @param where a description of the context of the exception 213 */ 214 private void debug(Throwable ex, String where) { 215 // Reserved for future logging 216 } 217 218 }