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 }