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