1 /* 2 * Copyright (c) 2005, 2019, 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 package sun.tools.attach; 26 27 import com.sun.tools.attach.AttachOperationFailedException; 28 import com.sun.tools.attach.AgentLoadException; 29 import com.sun.tools.attach.AttachNotSupportedException; 30 import com.sun.tools.attach.spi.AttachProvider; 31 32 import java.io.InputStream; 33 import java.io.IOException; 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 37 /* 38 * Solaris implementation of HotSpotVirtualMachine. 39 */ 40 public class VirtualMachineImpl extends HotSpotVirtualMachine { 41 // "/tmp" is used as a global well-known location for the files 42 // .java_pid<pid>. and .attach_pid<pid>. It is important that this 43 // location is the same for all processes, otherwise the tools 44 // will not be able to find all Hotspot processes. 45 // Any changes to this needs to be synchronized with HotSpot. 46 private static final String tmpdir = "/tmp"; 47 48 // door descriptor; 49 private int fd = -1; 50 String socket_path; 51 52 /** 53 * Attaches to the target VM 54 */ 55 VirtualMachineImpl(AttachProvider provider, String vmid) 56 throws AttachNotSupportedException, IOException 57 { 58 super(provider, vmid); 59 // This provider only understands process-ids (pids). 60 int pid; 61 try { 62 pid = Integer.parseInt(vmid); 63 if (pid < 1) { 64 throw new NumberFormatException(); 65 } 66 } catch (NumberFormatException x) { 67 throw new AttachNotSupportedException("Invalid process identifier: " + vmid); 68 } 69 70 // Opens the door file to the target VM. If the file is not 71 // found it might mean that the attach mechanism isn't started in the 72 // target VM so we attempt to start it and retry. 73 try { 74 fd = openDoor(pid); 75 } catch (FileNotFoundException fnf1) { 76 File f = createAttachFile(pid); 77 try { 78 sigquit(pid); 79 80 // give the target VM time to start the attach mechanism 81 final int delay_step = 100; 82 final long timeout = attachTimeout(); 83 long time_spend = 0; 84 long delay = 0; 85 do { 86 // Increase timeout on each attempt to reduce polling 87 delay += delay_step; 88 try { 89 Thread.sleep(delay); 90 } catch (InterruptedException x) { } 91 try { 92 fd = openDoor(pid); 93 } catch (FileNotFoundException fnf2) { 94 // pass 95 } 96 97 time_spend += delay; 98 if (time_spend > timeout/2 && fd == -1) { 99 // Send QUIT again to give target VM the last chance to react 100 sigquit(pid); 101 } 102 } while (time_spend <= timeout && fd == -1); 103 if (fd == -1) { 104 throw new AttachNotSupportedException( 105 String.format("Unable to open door %s: " + 106 "target process %d doesn't respond within %dms " + 107 "or HotSpot VM not loaded", socket_path, pid, time_spend)); 108 } 109 } finally { 110 f.delete(); 111 } 112 } 113 assert fd >= 0; 114 } 115 116 /** 117 * Detach from the target VM 118 */ 119 public void detach() throws IOException { 120 synchronized (this) { 121 if (fd != -1) { 122 close(fd); 123 fd = -1; 124 } 125 } 126 } 127 128 /** 129 * Execute the given command in the target VM. 130 */ 131 InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException { 132 assert args.length <= 3; // includes null 133 134 // first check that we are still attached 135 int door; 136 synchronized (this) { 137 if (fd == -1) { 138 throw new IOException("Detached from target VM"); 139 } 140 door = fd; 141 } 142 143 // enqueue the command via a door call 144 int s = enqueue(door, cmd, args); 145 assert s >= 0; // valid file descriptor 146 147 // The door call returns a file descriptor (one end of a socket pair). 148 // Create an input stream around it. 149 SocketInputStream sis = new SocketInputStream(s); 150 151 // Read the command completion status 152 int completionStatus; 153 try { 154 completionStatus = readInt(sis); 155 } catch (IOException ioe) { 156 sis.close(); 157 throw ioe; 158 } 159 160 // If non-0 it means an error but we need to special-case the 161 // "load" command to ensure that the right exception is thrown. 162 if (completionStatus != 0) { 163 // read from the stream and use that as the error message 164 String message = readErrorMessage(sis); 165 sis.close(); 166 if (cmd.equals("load")) { 167 String msg = "Failed to load agent library"; 168 if (!message.isEmpty()) 169 msg += ": " + message; 170 throw new AgentLoadException(msg); 171 } else { 172 if (message.isEmpty()) 173 message = "Command failed in target VM"; 174 throw new AttachOperationFailedException(message); 175 } 176 } 177 178 // Return the input stream so that the command output can be read 179 return sis; 180 } 181 182 // InputStream over a socket 183 private class SocketInputStream extends InputStream { 184 int s; 185 186 public SocketInputStream(int s) { 187 this.s = s; 188 } 189 190 public synchronized int read() throws IOException { 191 byte b[] = new byte[1]; 192 int n = this.read(b, 0, 1); 193 if (n == 1) { 194 return b[0] & 0xff; 195 } else { 196 return -1; 197 } 198 } 199 200 public synchronized int read(byte[] bs, int off, int len) throws IOException { 201 if ((off < 0) || (off > bs.length) || (len < 0) || 202 ((off + len) > bs.length) || ((off + len) < 0)) { 203 throw new IndexOutOfBoundsException(); 204 } else if (len == 0) 205 return 0; 206 207 return VirtualMachineImpl.read(s, bs, off, len); 208 } 209 210 public synchronized void close() throws IOException { 211 if (s != -1) { 212 int toClose = s; 213 s = -1; 214 VirtualMachineImpl.close(toClose); 215 } 216 } 217 } 218 219 // The door is attached to .java_pid<pid> in the temporary directory. 220 private int openDoor(int pid) throws IOException { 221 socket_path = tmpdir + "/.java_pid" + pid; 222 fd = open(socket_path); 223 224 // Check that the file owner/permission to avoid attaching to 225 // bogus process 226 try { 227 checkPermissions(socket_path); 228 } catch (IOException ioe) { 229 close(fd); 230 throw ioe; 231 } 232 return fd; 233 } 234 235 // On Solaris a simple handshake is used to start the attach mechanism 236 // if not already started. The client creates a .attach_pid<pid> file in the 237 // target VM's working directory (or temporary directory), and the SIGQUIT 238 // handler checks for the file. 239 private File createAttachFile(int pid) throws IOException { 240 String fn = ".attach_pid" + pid; 241 String path = "/proc/" + pid + "/cwd/" + fn; 242 File f = new File(path); 243 try { 244 f = f.getCanonicalFile(); 245 f.createNewFile(); 246 } catch (IOException x) { 247 f = new File(tmpdir, fn); 248 f.createNewFile(); 249 } 250 return f; 251 } 252 253 //-- native methods 254 255 static native int open(String path) throws IOException; 256 257 static native void close(int fd) throws IOException; 258 259 static native int read(int fd, byte buf[], int off, int buflen) throws IOException; 260 261 static native void checkPermissions(String path) throws IOException; 262 263 static native void sigquit(int pid) throws IOException; 264 265 // enqueue a command (and arguments) to the given door 266 static native int enqueue(int fd, String cmd, Object ... args) 267 throws IOException; 268 269 static { 270 System.loadLibrary("attach"); 271 } 272 }