1 /* 2 * Copyright (c) 2005, 2017, 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 26 package sun.tools.attach; 27 28 import com.sun.tools.attach.AttachNotSupportedException; 29 import com.sun.tools.attach.VirtualMachine; 30 import com.sun.tools.attach.AgentLoadException; 31 import com.sun.tools.attach.AgentInitializationException; 32 import com.sun.tools.attach.spi.AttachProvider; 33 import jdk.internal.misc.VM; 34 35 import java.io.BufferedReader; 36 import java.io.InputStream; 37 import java.io.IOException; 38 import java.io.InputStreamReader; 39 import java.security.AccessController; 40 import java.security.PrivilegedAction; 41 import java.util.Properties; 42 import java.util.stream.Collectors; 43 44 /* 45 * The HotSpot implementation of com.sun.tools.attach.VirtualMachine. 46 */ 47 48 public abstract class HotSpotVirtualMachine extends VirtualMachine { 49 50 private static final long CURRENT_PID; 51 private static final boolean ALLOW_ATTACH_SELF; 52 static { 53 PrivilegedAction<ProcessHandle> pa = ProcessHandle::current; 54 CURRENT_PID = AccessController.doPrivileged(pa).pid(); 55 56 String s = VM.getSavedProperty("jdk.attach.allowAttachSelf"); 57 ALLOW_ATTACH_SELF = "".equals(s) || Boolean.parseBoolean(s); 58 } 59 60 HotSpotVirtualMachine(AttachProvider provider, String id) 61 throws AttachNotSupportedException, IOException 62 { 63 super(provider, id); 64 65 int pid; 66 try { 67 pid = Integer.parseInt(id); 68 } catch (NumberFormatException e) { 69 throw new AttachNotSupportedException("Invalid process identifier"); 70 } 71 72 // The tool should be a different VM to the target. This check will 73 // eventually be enforced by the target VM. 74 if (!ALLOW_ATTACH_SELF && (pid == 0 || pid == CURRENT_PID)) { 75 throw new IOException("Can not attach to current VM"); 76 } 77 } 78 79 /* 80 * Load agent library 81 * If isAbsolute is true then the agent library is the absolute path 82 * to the library and thus will not be expanded in the target VM. 83 * if isAbsolute is false then the agent library is just a library 84 * name and it will be expended in the target VM. 85 */ 86 private void loadAgentLibrary(String agentLibrary, boolean isAbsolute, String options) 87 throws AgentLoadException, AgentInitializationException, IOException 88 { 89 InputStream in = execute("load", 90 agentLibrary, 91 isAbsolute ? "true" : "false", 92 options); 93 try { 94 int result = readInt(in); 95 if (result != 0) { 96 throw new AgentInitializationException("Agent_OnAttach failed", result); 97 } 98 } finally { 99 in.close(); 100 101 } 102 } 103 104 /* 105 * Load agent library - library name will be expanded in target VM 106 */ 107 public void loadAgentLibrary(String agentLibrary, String options) 108 throws AgentLoadException, AgentInitializationException, IOException 109 { 110 loadAgentLibrary(agentLibrary, false, options); 111 } 112 113 /* 114 * Load agent - absolute path of library provided to target VM 115 */ 116 public void loadAgentPath(String agentLibrary, String options) 117 throws AgentLoadException, AgentInitializationException, IOException 118 { 119 loadAgentLibrary(agentLibrary, true, options); 120 } 121 122 /* 123 * Load JPLIS agent which will load the agent JAR file and invoke 124 * the agentmain method. 125 */ 126 public void loadAgent(String agent, String options) 127 throws AgentLoadException, AgentInitializationException, IOException 128 { 129 String args = agent; 130 if (options != null) { 131 args = args + "=" + options; 132 } 133 try { 134 loadAgentLibrary("instrument", args); 135 } catch (AgentInitializationException x) { 136 /* 137 * Translate interesting errors into the right exception and 138 * message (FIXME: create a better interface to the instrument 139 * implementation so this isn't necessary) 140 */ 141 int rc = x.returnValue(); 142 switch (rc) { 143 case JNI_ENOMEM: 144 throw new AgentLoadException("Insuffient memory"); 145 case ATTACH_ERROR_BADJAR: 146 throw new AgentLoadException( 147 "Agent JAR not found or no Agent-Class attribute"); 148 case ATTACH_ERROR_NOTONCP: 149 throw new AgentLoadException( 150 "Unable to add JAR file to system class path"); 151 case ATTACH_ERROR_STARTFAIL: 152 throw new AgentInitializationException( 153 "Agent JAR loaded but agent failed to initialize"); 154 default : 155 throw new AgentLoadException("" + 156 "Failed to load agent - unknown reason: " + rc); 157 } 158 } 159 } 160 161 /* 162 * The possible errors returned by JPLIS's agentmain 163 */ 164 private static final int JNI_ENOMEM = -4; 165 private static final int ATTACH_ERROR_BADJAR = 100; 166 private static final int ATTACH_ERROR_NOTONCP = 101; 167 private static final int ATTACH_ERROR_STARTFAIL = 102; 168 169 170 /* 171 * Send "properties" command to target VM 172 */ 173 public Properties getSystemProperties() throws IOException { 174 InputStream in = null; 175 Properties props = new Properties(); 176 try { 177 in = executeCommand("properties"); 178 props.load(in); 179 } finally { 180 if (in != null) in.close(); 181 } 182 return props; 183 } 184 185 public Properties getAgentProperties() throws IOException { 186 InputStream in = null; 187 Properties props = new Properties(); 188 try { 189 in = executeCommand("agentProperties"); 190 props.load(in); 191 } finally { 192 if (in != null) in.close(); 193 } 194 return props; 195 } 196 197 private static final String MANAGEMENT_PREFIX = "com.sun.management."; 198 199 private static boolean checkedKeyName(Object key) { 200 if (!(key instanceof String)) { 201 throw new IllegalArgumentException("Invalid option (not a String): "+key); 202 } 203 if (!((String)key).startsWith(MANAGEMENT_PREFIX)) { 204 throw new IllegalArgumentException("Invalid option: "+key); 205 } 206 return true; 207 } 208 209 private static String stripKeyName(Object key) { 210 return ((String)key).substring(MANAGEMENT_PREFIX.length()); 211 } 212 213 @Override 214 public void startManagementAgent(Properties agentProperties) throws IOException { 215 if (agentProperties == null) { 216 throw new NullPointerException("agentProperties cannot be null"); 217 } 218 // Convert the arguments into arguments suitable for the Diagnostic Command: 219 // "ManagementAgent.start jmxremote.port=5555 jmxremote.authenticate=false" 220 String args = agentProperties.entrySet().stream() 221 .filter(entry -> checkedKeyName(entry.getKey())) 222 .map(entry -> stripKeyName(entry.getKey()) + "=" + escape(entry.getValue())) 223 .collect(Collectors.joining(" ")); 224 executeJCmd("ManagementAgent.start " + args).close(); 225 } 226 227 private String escape(Object arg) { 228 String value = arg.toString(); 229 if (value.contains(" ")) { 230 return "'" + value + "'"; 231 } 232 return value; 233 } 234 235 @Override 236 public String startLocalManagementAgent() throws IOException { 237 executeJCmd("ManagementAgent.start_local").close(); 238 String prop = MANAGEMENT_PREFIX + "jmxremote.localConnectorAddress"; 239 return getAgentProperties().getProperty(prop); 240 } 241 242 243 // --- HotSpot specific methods --- 244 245 // same as SIGQUIT 246 public void localDataDump() throws IOException { 247 executeCommand("datadump").close(); 248 } 249 250 // Remote ctrl-break. The output of the ctrl-break actions can 251 // be read from the input stream. 252 public InputStream remoteDataDump(Object ... args) throws IOException { 253 return executeCommand("threaddump", args); 254 } 255 256 // Remote heap dump. The output (error message) can be read from the 257 // returned input stream. 258 public InputStream dumpHeap(Object ... args) throws IOException { 259 return executeCommand("dumpheap", args); 260 } 261 262 // Heap histogram (heap inspection in HotSpot) 263 public InputStream heapHisto(Object ... args) throws IOException { 264 return executeCommand("inspectheap", args); 265 } 266 267 // set JVM command line flag 268 public InputStream setFlag(String name, String value) throws IOException { 269 return executeCommand("setflag", name, value); 270 } 271 272 // print command line flag 273 public InputStream printFlag(String name) throws IOException { 274 return executeCommand("printflag", name); 275 } 276 277 public InputStream executeJCmd(String command) throws IOException { 278 return executeCommand("jcmd", command); 279 } 280 281 282 // -- Supporting methods 283 284 /* 285 * Execute the given command in the target VM - specific platform 286 * implementation must implement this. 287 */ 288 abstract InputStream execute(String cmd, Object ... args) 289 throws AgentLoadException, IOException; 290 291 /* 292 * Convenience method for simple commands 293 */ 294 public InputStream executeCommand(String cmd, Object ... args) throws IOException { 295 try { 296 return execute(cmd, args); 297 } catch (AgentLoadException x) { 298 throw new InternalError("Should not get here", x); 299 } 300 } 301 302 303 /* 304 * Utility method to read an 'int' from the input stream. Ideally 305 * we should be using java.util.Scanner here but this implementation 306 * guarantees not to read ahead. 307 */ 308 int readInt(InputStream in) throws IOException { 309 StringBuilder sb = new StringBuilder(); 310 311 // read to \n or EOF 312 int n; 313 byte buf[] = new byte[1]; 314 do { 315 n = in.read(buf, 0, 1); 316 if (n > 0) { 317 char c = (char)buf[0]; 318 if (c == '\n') { 319 break; // EOL found 320 } else { 321 sb.append(c); 322 } 323 } 324 } while (n > 0); 325 326 if (sb.length() == 0) { 327 throw new IOException("Premature EOF"); 328 } 329 330 int value; 331 try { 332 value = Integer.parseInt(sb.toString()); 333 } catch (NumberFormatException x) { 334 throw new IOException("Non-numeric value found - int expected"); 335 } 336 return value; 337 } 338 339 /* 340 * Utility method to read data into a String. 341 */ 342 String readErrorMessage(InputStream in) throws IOException { 343 String s; 344 StringBuilder message = new StringBuilder(); 345 BufferedReader br = new BufferedReader(new InputStreamReader(in)); 346 while ((s = br.readLine()) != null) { 347 message.append(s); 348 } 349 return message.toString(); 350 } 351 352 353 // -- attach timeout support 354 355 private static long defaultAttachTimeout = 10000; 356 private volatile long attachTimeout; 357 358 /* 359 * Return attach timeout based on the value of the sun.tools.attach.attachTimeout 360 * property, or the default timeout if the property is not set to a positive 361 * value. 362 */ 363 long attachTimeout() { 364 if (attachTimeout == 0) { 365 synchronized(this) { 366 if (attachTimeout == 0) { 367 try { 368 String s = 369 System.getProperty("sun.tools.attach.attachTimeout"); 370 attachTimeout = Long.parseLong(s); 371 } catch (SecurityException se) { 372 } catch (NumberFormatException ne) { 373 } 374 if (attachTimeout <= 0) { 375 attachTimeout = defaultAttachTimeout; 376 } 377 } 378 } 379 } 380 return attachTimeout; 381 } 382 }