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