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
26package sun.tools.attach;
27
28import com.sun.tools.attach.AttachNotSupportedException;
29import com.sun.tools.attach.VirtualMachine;
30import com.sun.tools.attach.AgentLoadException;
31import com.sun.tools.attach.AgentInitializationException;
32import com.sun.tools.attach.spi.AttachProvider;
33import jdk.internal.misc.VM;
34
35import java.io.BufferedReader;
36import java.io.InputStream;
37import java.io.IOException;
38import java.io.InputStreamReader;
39import java.security.AccessController;
40import java.security.PrivilegedAction;
41import java.util.Properties;
42import java.util.stream.Collectors;
43
44/*
45 * The HotSpot implementation of com.sun.tools.attach.VirtualMachine.
46 */
47
48public 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}
383