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 ECLogger log; 48 private final Connector connector; 49 private final String remoteAgent; 50 private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs; 51 52 /** 53 * Start the remote agent and establish a JDI connection to it. 54 * 55 * @param log the logger 56 * @param port the socket port for (non-JDI) commands 57 * @param remoteVMOptions any user requested VM options 58 * @param remoteAgent full class name of remote agent to launch 59 * @param isLaunch does JDI do the launch? That is, LaunchingConnector, 60 * otherwise we start explicitly and use ListeningConnector 61 */ 62 public JDIInitiator(ECLogger log, int port, List<String> remoteVMOptions, 63 String remoteAgent, boolean isLaunch) { 64 this.log = log; 65 this.remoteAgent = remoteAgent; 66 String connectorName 67 = isLaunch 68 ? "com.sun.jdi.CommandLineLaunch" 69 : "com.sun.jdi.SocketListen"; 70 this.connector = findConnector(connectorName); 71 if (connector == null) { 72 throw new IllegalArgumentException("No connector named: " + connectorName); 73 } 74 Map<String, String> argumentName2Value 75 = isLaunch 76 ? launchArgs(port, String.join(" ", remoteVMOptions)) 77 : new HashMap<>(); 78 this.connectorArgs = mergeConnectorArgs(connector, argumentName2Value); 79 this.vm = isLaunch 80 ? launchTarget() 81 : listenTarget(port, remoteVMOptions); 82 83 } 84 85 /** 86 * Returns the resulting {@code VirtualMachine} instance. 87 * 88 * @return the virtual machine 89 */ 90 public VirtualMachine vm() { 91 return vm; 92 } 93 94 /** 95 * Returns the launched process. 96 * 97 * @return the remote agent process 98 */ 99 public Process process() { 100 return process; 101 } 102 103 /* launch child target vm */ 104 private VirtualMachine launchTarget() { 105 LaunchingConnector launcher = (LaunchingConnector) connector; 106 try { 107 VirtualMachine new_vm = launcher.launch(connectorArgs); 108 process = new_vm.process(); 109 return new_vm; 110 } catch (Exception ex) { 111 reportLaunchFail(ex, "launch"); 112 } 113 return null; 114 } 115 116 /** 117 * Directly launch the remote agent and connect JDI to it with a 118 * ListeningConnector. 119 */ 120 private VirtualMachine listenTarget(int port, List<String> remoteVMOptions) { 121 ListeningConnector listener = (ListeningConnector) connector; 122 try { 123 // Start listening, get the JDI connection address 124 String addr = listener.startListening(connectorArgs); 125 log.debug("Listening at address: " + addr); 126 127 // Launch the RemoteAgent requesting a connection on that address 128 String javaHome = System.getProperty("java.home"); 129 List<String> args = new ArrayList<>(); 130 args.add(javaHome == null 131 ? "java" 132 : javaHome + File.separator + "bin" + File.separator + "java"); 133 args.add("-agentlib:jdwp=transport=" + connector.transport().name() + 134 ",address=" + addr); 135 args.addAll(remoteVMOptions); 136 args.add(remoteAgent); 137 args.add("" + port); 138 ProcessBuilder pb = new ProcessBuilder(args); 139 process = pb.start(); 140 141 // Forward out, err, and in 142 // Accept the connection from the remote agent 143 vm = listener.accept(connectorArgs); 144 listener.stopListening(connectorArgs); 145 return vm; 146 } catch (Exception ex) { 147 reportLaunchFail(ex, "listen"); 148 } 149 return null; 150 } 151 152 private Connector findConnector(String name) { 153 for (Connector cntor 154 : Bootstrap.virtualMachineManager().allConnectors()) { 155 if (cntor.name().equals(name)) { 156 return cntor; 157 } 158 } 159 return null; 160 } 161 162 private Map<String, Connector.Argument> mergeConnectorArgs(Connector connector, Map<String, String> argumentName2Value) { 163 Map<String, Connector.Argument> arguments = connector.defaultArguments(); 164 165 for (Entry<String, String> argumentEntry : argumentName2Value.entrySet()) { 166 String name = argumentEntry.getKey(); 167 String value = argumentEntry.getValue(); 168 Connector.Argument argument = arguments.get(name); 169 170 if (argument == null) { 171 throw new IllegalArgumentException("Argument is not defined for connector:" + 172 name + " -- " + connector.name()); 173 } 174 175 argument.setValue(value); 176 } 177 178 return arguments; 179 } 180 181 /** 182 * The JShell specific Connector args for the LaunchingConnector. 183 * 184 * @param portthe socket port for (non-JDI) commands 185 * @param remoteVMOptions any user requested VM options 186 * @return the argument map 187 */ 188 private Map<String, String> launchArgs(int port, String remoteVMOptions) { 189 Map<String, String> argumentName2Value = new HashMap<>(); 190 argumentName2Value.put("main", remoteAgent + " " + port); 191 argumentName2Value.put("options", remoteVMOptions); 192 return argumentName2Value; 193 } 194 195 private void reportLaunchFail(Exception ex, String context) { 196 throw new InternalError("Failed remote " + context + ": " + connector + 197 " -- " + connectorArgs, ex); 198 } 199 200 }