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 * @param host explicit hostname to use, if null use discovered |
|
60 * hostname, applies to listening only (!isLaunch) |
|
61 */ |
|
62 public JDIInitiator(int port, List<String> remoteVMOptions, String remoteAgent, |
|
63 boolean isLaunch, String host) { |
|
64 this.remoteAgent = remoteAgent; |
|
65 String connectorName |
|
66 = isLaunch |
|
67 ? "com.sun.jdi.CommandLineLaunch" |
|
68 : "com.sun.jdi.SocketListen"; |
|
69 this.connector = findConnector(connectorName); |
|
70 if (connector == null) { |
|
71 throw new IllegalArgumentException("No connector named: " + connectorName); |
|
72 } |
|
73 Map<String, String> argumentName2Value |
|
74 = isLaunch |
|
75 ? launchArgs(port, String.join(" ", remoteVMOptions)) |
|
76 : new HashMap<>(); |
|
77 if (host != null && !isLaunch) { |
|
78 argumentName2Value.put("localAddress", host); |
|
79 } |
|
80 this.connectorArgs = mergeConnectorArgs(connector, argumentName2Value); |
|
81 this.vm = isLaunch |
|
82 ? launchTarget() |
|
83 : listenTarget(port, remoteVMOptions); |
|
84 |
|
85 } |
|
86 |
|
87 /** |
|
88 * Returns the resulting {@code VirtualMachine} instance. |
|
89 * |
|
90 * @return the virtual machine |
|
91 */ |
|
92 public VirtualMachine vm() { |
|
93 return vm; |
|
94 } |
|
95 |
|
96 /** |
|
97 * Returns the launched process. |
|
98 * |
|
99 * @return the remote agent process |
|
100 */ |
|
101 public Process process() { |
|
102 return process; |
|
103 } |
|
104 |
|
105 /* launch child target vm */ |
|
106 private VirtualMachine launchTarget() { |
|
107 LaunchingConnector launcher = (LaunchingConnector) connector; |
|
108 try { |
|
109 VirtualMachine new_vm = launcher.launch(connectorArgs); |
|
110 process = new_vm.process(); |
|
111 return new_vm; |
|
112 } catch (Exception ex) { |
|
113 reportLaunchFail(ex, "launch"); |
|
114 } |
|
115 return null; |
|
116 } |
|
117 |
|
118 /** |
|
119 * Directly launch the remote agent and connect JDI to it with a |
|
120 * ListeningConnector. |
|
121 */ |
|
122 private VirtualMachine listenTarget(int port, List<String> remoteVMOptions) { |
|
123 ListeningConnector listener = (ListeningConnector) connector; |
|
124 try { |
|
125 // Start listening, get the JDI connection address |
|
126 String addr = listener.startListening(connectorArgs); |
|
127 debug("Listening at address: " + addr); |
|
128 |
|
129 // Launch the RemoteAgent requesting a connection on that address |
|
130 String javaHome = System.getProperty("java.home"); |
|
131 List<String> args = new ArrayList<>(); |
|
132 args.add(javaHome == null |
|
133 ? "java" |
|
134 : javaHome + File.separator + "bin" + File.separator + "java"); |
|
135 args.add("-agentlib:jdwp=transport=" + connector.transport().name() + |
|
136 ",address=" + addr); |
|
137 args.addAll(remoteVMOptions); |
|
138 args.add(remoteAgent); |
|
139 args.add("" + port); |
|
140 ProcessBuilder pb = new ProcessBuilder(args); |
|
141 process = pb.start(); |
|
142 |
|
143 // Forward out, err, and in |
|
144 // Accept the connection from the remote agent |
|
145 vm = listener.accept(connectorArgs); |
|
146 listener.stopListening(connectorArgs); |
|
147 return vm; |
|
148 } catch (Exception ex) { |
|
149 reportLaunchFail(ex, "listen"); |
|
150 } |
|
151 return null; |
|
152 } |
|
153 |
|
154 private Connector findConnector(String name) { |
|
155 for (Connector cntor |
|
156 : Bootstrap.virtualMachineManager().allConnectors()) { |
|
157 if (cntor.name().equals(name)) { |
|
158 return cntor; |
|
159 } |
|
160 } |
|
161 return null; |
|
162 } |
|
163 |
|
164 private Map<String, Connector.Argument> mergeConnectorArgs(Connector connector, Map<String, String> argumentName2Value) { |
|
165 Map<String, Connector.Argument> arguments = connector.defaultArguments(); |
|
166 |
|
167 for (Entry<String, String> argumentEntry : argumentName2Value.entrySet()) { |
|
168 String name = argumentEntry.getKey(); |
|
169 String value = argumentEntry.getValue(); |
|
170 Connector.Argument argument = arguments.get(name); |
|
171 |
|
172 if (argument == null) { |
|
173 throw new IllegalArgumentException("Argument is not defined for connector:" + |
|
174 name + " -- " + connector.name()); |
|
175 } |
|
176 |
|
177 argument.setValue(value); |
|
178 } |
|
179 |
|
180 return arguments; |
|
181 } |
|
182 |
|
183 /** |
|
184 * The JShell specific Connector args for the LaunchingConnector. |
|
185 * |
|
186 * @param portthe socket port for (non-JDI) commands |
|
187 * @param remoteVMOptions any user requested VM options |
|
188 * @return the argument map |
|
189 */ |
|
190 private Map<String, String> launchArgs(int port, String remoteVMOptions) { |
|
191 Map<String, String> argumentName2Value = new HashMap<>(); |
|
192 argumentName2Value.put("main", remoteAgent + " " + port); |
|
193 argumentName2Value.put("options", remoteVMOptions); |
|
194 return argumentName2Value; |
|
195 } |
|
196 |
|
197 private void reportLaunchFail(Exception ex, String context) { |
|
198 throw new InternalError("Failed remote " + context + ": " + connector + |
|
199 " -- " + connectorArgs, ex); |
|
200 } |
|
201 |
|
202 /** |
|
203 * Log debugging information. Arguments as for {@code printf}. |
|
204 * |
|
205 * @param format a format string as described in Format string syntax |
|
206 * @param args arguments referenced by the format specifiers in the format |
|
207 * string. |
|
208 */ |
|
209 private void debug(String format, Object... args) { |
|
210 // Reserved for future logging |
|
211 } |
|
212 |
|
213 /** |
|
214 * Log a serious unexpected internal exception. |
|
215 * |
|
216 * @param ex the exception |
|
217 * @param where a description of the context of the exception |
|
218 */ |
|
219 private void debug(Throwable ex, String where) { |
|
220 // Reserved for future logging |
|
221 } |
|
222 |
|
223 } |
|