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