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