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 }