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